Express Typescript Mongodb Project From Scratch
In this post you will learn how to implement Mongodb Typescript connection in the real Express + NodeJS application. So what we want to implement here is a typical API inside NodeJS with Express (obviously covered with Typescript) and Mongodb for our database.
The project
And here I already prepared for us an empty project. It just has an empty package.json
and a Typescript config.
{
"compilerOptions": {
"module": "commonjs",
"esModuleInterop": true,
"target": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist",
"strict": true
},
"lib": ["es2015"]
}
Here is a standard Typescript config for our NodeJS application. This is why our module
is set to commonjs
and our moduleResolution
to node
. Also it is important that we are using here strict
mode which means our Typescript will be as strict as possible.
Installing dependencies
Our first step is to install nodemon
.
This is a package which allows us to restart our application with every single change.
npm i nodemon -D
We install it only as a development dependency because we don't need it in production.
"scripts": {
"start": "nodemon main.ts"
}
Here we added a script to call our main file with nodemon inside our package.json
.
This code won't work because we try to execute a Typescript file. nodemon
can transpile Typescript files but we need to install an additional dependency for this.
npm i ts-node -D
Here we installed ts-node
as a dev dependency so nodemon
can transpile it.
Our next step here is to install Express
. We need express because it's a framework that we want to use in order to build our API.
npm i express
And additionally to that we want to install types for Express so Typescript can help us.
npm i @types/express -D
Now we can create a basic project in our main.ts
.
import express from 'express'
const app = express()
app.listen(3012, () => {
console.log('API is started')
})
Now when we call npm start
our application is started and our API is available for us.
Installing MongoDB
Now we want to implement Typescript MongoDB connection. For this we want to install a MongoDB package but there are lots of different libraries for this. We will use a native driver which is quite low level but it will show you how to use MongoDB correctly.
But in order to start working with MongoDB you must install MongoDB package on your machine and start MongoDB.
I already have a running MongoDB so now I need to install a dependency of MongoDB
npm i mongodb
Also I want to mention here that we don't need types as they are included together with the package.
Now I want to created an additional file db.js
with several methods which will help us to work with the database.
import { Db, MongoClient } from "mongodb";
const state: { db: Db | null } = { db: null };
export const connect = async (url: string, dbname: string): Promise<void> => {
try {
if (state.db) {
return;
}
const client = new MongoClient(url);
await client.connect();
state.db = client.db(dbname);
} catch (err) {
console.error(err);
}
};
export const get = (): Db => {
if (!state.db) {
throw new Error("Connection is not initialized");
}
return state.db;
};
Here we have 3 different things. First of all an object state
where we will store a reference to our database. We also create a connect
method which creates a MongoClient connection and saved a reference to state.db
. Also we have a get
method which returns us a reference to our database so we can get it from any place of our application.
Our database helper is fully ready. Now we need to connect to database before we even start our application.
const startServer = async () => {
await connect("mongodb://localhost:27017/api", "app");
app.listen(3012, () => {
console.log("API is started");
});
};
startServer();
With such code we are waiting for successful MongoDB connection before we start our API.
Creating a type
What I want to do now is create models and controllers. What does it mean? For our API we want to define functions which respond with some data. These are our controllers. Our models is something which is responsible for working with the database.
Models is just a bunch of helpers to work with database more effeciently.
Before we start with controllers let's create an additional type for our entity.
export type Artist = {
_id: string
name: string
}
Additionally to that we need a type for not saved Artist.
export type ArtistWithoutId = Omit<Artist, "_id">;
Here we just omitted an _id
for unsaved record.
Creating a model
Let's start with creating a model which is responsible for managing artists in the database.
// src/models/artists.ts
import { Artist } from "../types/artist";
import * as db from "../db";
export const all = (): Promise<Artist[]> => {
return db.get().collection("artists").find<Artist>({}).toArray();
};
We have here a all
method which gets a list of artists from the database.
Any database request is an asynchronous request so it will return a promise.
Creating controller
First of all let's create a controller for entity artists. Inside we will write all methods which are responsible for artists API.
import * as Artists from "../models/artists";
import {
NextFunction,
Request as ExpressRequest,
Response as ExpressResponse,
} from "express";
export const all = async (
_: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const docs = await Artists.all();
res.send(docs);
} catch (err) {
next();
}
};
Our all
method inside controller is responsible for responding to our API call. Inside it uses model so we don't work with database directly in the controller. Inside Express all our route callbacks get req
, res
and next
as parameters
The last step is to register our route in the application.
import * as artistsController from "./controllers/artists";
const app = express();
app.get("/artists", artistsController.all);
As you can see in Postman we got an empty array as a response which is totally fine because we don't have any records on our database.
Creating an artist
Now we want to implement creation of our artist. But in order to do that we must install one more package to parse a body of the request.
npm i body-parser
Now we must configure it correctly.
import bodyParser from "body-parser";
const app = express();
app.use(bodyParser.json());
...
Now we will get body
inside our request.
In order to implement create artist we must do exactly the same stuff as with all
. It will be a method in our model and a method in our controller.
// src/models/artist.ts
export const create = async (artist: ArtistWithoutId): Promise<Artist> => {
await db.get().collection("artists").insertOne(artist);
return artist as Artist;
};
Here we use insertOne
to create our new artist in the database. Keep in mind that insertOne
doesn't return us a created record but changes our artist
property instead.
// src/controllers/artist.ts
export const create = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const doc = await Artists.create({ name: req.body.name });
res.send(doc);
} catch (err) {
next();
}
};
Our create
method in controller is extremely similar to all
method. We call our model and send the response to our client.
// src/main.ts
app.post("/artists", artistsController.create);
And last but not least is we register our new route.
As you can see in browser our artist was successfully created.
Finding an artist
Similar code we need to implement getting of the artist by ID.
// src/models/artists.ts
export const findById = (id: string): Promise<Artist | null> => {
return db
.get()
.collection("artists")
.findOne<Artist>({ _id: new ObjectId(id) });
};
We find here an artist by ID. Most importantly we must wrap our id
with new ObjectId
because this is how MongoDB stores IDs.
// src/controllers/artist.ts
export const findById = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const doc = await Artists.findById(req.params.id);
res.send(doc);
} catch (err) {
next();
}
};
Getting an artist by ID is exactly like previous methods in controller.
// src/main.ts
app.get("/artists/:id", artistsController.findById);
And here we registered our new route.
As you can see in browser we can find our artists by ID now.
Updating an artist
Another method what we must implement is updating our artist.
// src/models/artists.ts
export const update = async (
id: string,
newData: ArtistWithoutId
): Promise<Artist> => {
await db
.get()
.collection("artists")
.updateOne({ _id: new ObjectId(id) }, { $set: newData });
return {
...newData,
_id: id,
};
};
In our create
we pass the ID that we want to update and an object with new data. This method doesn't return the update artist so we merge it and return ourselves.
// src/controllers/artist.ts
export const update = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
const doc = await Artists.update(req.params.id, { name: req.body.name });
res.send(doc);
} catch (err) {
next();
}
};
Our controller method is extremely similar to previous methods.
// src/main.ts
app.put("/artists/:id", artistsController.update);
Here is our new route to update the user.
As you can see now we can update our artist by ID.
Deleting an artist
The last route that we need to implement is deleting an artist by ID.
// src/models/artists.ts
export const deleteById = (id: string): Promise<DeleteResult> => {
return db
.get()
.collection("artists")
.deleteOne({ _id: new ObjectId(id) });
};
We call deleteOne
and again wrap our id
with new ObjectId
in order to work with MongoDB.
// src/controllers/artist.ts
export const deleteById = async (
req: ExpressRequest,
res: ExpressResponse,
next: NextFunction
) => {
try {
await Artists.deleteById(req.params.id);
res.sendStatus(200);
} catch (err) {
next();
}
};
Our controller just calls a model and returns 200
status if there was no error.
// src/main.ts
app.delete("/artists/:id", artistsController.deleteById);
The last step here is to register our new route for deletion.
As you can see in Postman we can remove our create artist by ID now.
Want to conquer your next JavaScript interview? Download my FREE PDF - Pass Your JS Interview with Confidence and start preparing for success today!
📚 Source code of what we've done