So, you’re on the TypeScript
bandwagon and you want to use Mongodb
and mongoose
.
Great! So, now you need to define your schema and model. So, what is a good TypeScript developer to do?
Well, first we turn to npm to install the dependencies and declaration files.
This took me awhile to get right, so I hope it saves you some time.
Getting Started
We’ll start by using npm to install mongoose and the TypeScript declaration files using the new @types definitions with TypeScript 2.
$ npm install mongoose --save
$ npm install @types/mongoose --save-dev
$ npm install @types/mongodb --save-dev
Interface
First, let’s create a public interface for our model.
In this example I am going to be using a: User
.
I created a new file at src/interfaces/user.ts:
export interface IUser {
email?: string;
firstName?: string;
lastName?: string;
}
This simply represents a user, and will be helpful when populating our mongodb collections.
Schema + Model
For the schema and model, I put both of these into a new file at src/schemas/user.ts:
import { Document, Schema, Model, model} from "mongoose";
import { IUser } from "../interfaces/user";
export interface IUserModel extends IUser, Document {
fullName(): string;
}
export var UserSchema: Schema = new Schema({
createdAt: Date,
email: String,
firstName: String,
lastName: String
});
UserSchema.pre("save", function(next) {
let now = new Date();
if (!this.createdAt) {
this.createdAt = now;
}
next();
});
UserSchema.methods.fullName = function(): string {
return (this.firstName.trim() + " " + this.lastName.trim());
};
export const User: Model<IUserModel> = model<IUserModel>("User", UserSchema);
Let’s dig into what is going on here:
- First, I am importing the
Document
,Schema
andModel
classes, as well as the model() function. - I am also importing our
IUser
interface that we created previously. - Next, I create the
IUserModel
. Note that it extends both theIUser
interface as well as theDocument
class. - Next, I create the
Schema
. Unfortunately, you have to list all of the schema properties again. This means that whenever you modify the schema, you will need to modify the interface as well, and vice-versa. - Next, I define a pre-save hook to populate the
created_at
date. - Next, I am defining a instance method. In this case I have a simple
fullname()
method that returns the first name concatenated with the user’s last name. - Lastly, using the
UserSchema
we create a new model using mongoose’smodel()
function.
In Action
Ok, now let’s see this in action. I have a simple REST API for the user defined in an expressjs app.
Here is the get()
method that is routed to the /users/:id
GET request.
/**
* Get a user
*
* @class UsersApi
* @method get
* @param req {Request} The express request object.
* @param res {Response} The express response object.
* @param next {NextFunction} The next function to continue.
*/
public get(req: Request, res: Response, next: NextFunction) {
//verify the id parameter exists
const PARAM_ID: string = "id";
if (typeof req.params[PARAM_ID] === "undefined" || req.params[PARAM_ID] === null) {
res.sendStatus(404);
next();
return;
}
//get the id
var id = req.params[PARAM_ID];
//get authorized user
this.authorize(req, res, next).then((user: IUserModel) => {
//make sure the user being deleted is the authorized user
if (user._id !== id) {
res.sendStatus(401);
next();
return;
}
//log
console.log(`[UsersApi.get] Retrieving user: {id: ${req.params.id}}.`);
//find user
User.findById(id).then((user: IUserModel) => {
//verify user was found
if (user === null) {
res.sendStatus(404);
next();
return;
}
//send json response
res.json(user);
next();
}).catch(next);
}).catch(next);
}
Here is what I am doing in the get() method:
- First, I verify that the id request parameter was provided. If not, the user get’s a 404 Not Found.
- Next, I have a method named “authorize” in a base class with the following signature:
authorize(req: Request, res: Response, next: NextFunction): Promise<IUserModel>
. Note that it returns a Promise of our user model,IUserModel
. - Assuming the user is authorized via a token sent in the headers with every request to the API we have a
IUserModel
object (similar to what we created above). - I think make sure that the authorized user is the one requesting to GET the user data. If not, sorry buddy, but you just get a 401 Unauthorized response.
- Ok, now that we made sure everything is in order, let’s do a bit of logging and then get the user from mongodb. Here I use the
User
model to find the document based on theid
parameter. - Next, we verify that the user document returned is not null. If it is, then we send back a 404 Not Found response.
- Last, we return the json of the user document and call the
next()
method.