TypeScript Express tutorial #2. MongoDB, models and environment variables

Express JavaScript

This entry is part 2 of 12 in the TypeScript Express tutorial

In this part of the Express TypeScript tutorial, we cover using a MongoDB database. To do that properly, we also learn the concept of environment variables. Since we follow the MVC pattern, we introduce the idea of a model. Just as in the previous part of the tutorial, all of the code from this part is in the express-typescript repository. Feel free to give it a star if you have not yet done so.

TypeScript Express tutorial #2: Express MongoDB

To use Express MongoDB in this article, we use a platform called mLab. It provides hosting for your database and has a free plan also, called the sandbox. After creating a new database, you can see its URI. In my case it is  .

The URI includes the dbuser and dbpassword. These are not the credentials to your mLab account, but to the database itself. To create a user for your database, go to the tab named users.

In this Express MongoDB part of the tutorial, we use Mongoose. It is an Object Data Modeling (ODM) library that makes interacting with your MongoDB easier.

The question is how to provide credentials to our application. Even though for some the first thought might be to hardcode it into our code, it is not that good of an idea for various reasons. First of them is that you probably wouldn’t want to submit the credentials to your repository. The second reason is that you might wish to have different databases for each of your environment, for example on production and staging. The solution here is using environment variables.

Environment variables

The environment variables are values accessible in the environment in which the program runs. The operating system sets some of them up when it boots. The variables can be either global or just inside of one process. In Node.js, you have access to them through the    global object.

The easiest way to set them is to do it in your NPM scripts:

That does not seem like a good solution either. Another approach that you can take is to create a file named   in which you store all your environment variables. The most popular way to handle a file like that is to use the dotenv package. First, create the   file in the parent directory of your project.

.env

As you can see, I’ve also created the PORT variable. It might be useful if you would want to change the port on which your application runs

At the beginning of your  file (the one that is the entrypoint of your project) import  . This import runs the config function for you and loads the   file. The only thing left to establish the connection to our database is to run the  function.

src/server.ts

It is a convention for environmental variables to be in uppercase.

Since you definitely shouldn’t commit your  file, I recommend adding it to your .

Validating environment variables

Forgetting to set one of your environment variables might cause your application to malfunction. To prevent it, we can verify them. To do that, I use the envalid package.

src/utils/validateEnv.ts

It throws an error if you if you forgot to provide one of the defined variables or if they are of a wrong type.

You can put the initialization of the database connection in the App class, and the validation of environment variables in the server.ts file.

src/server.ts

src/app.ts

Express MongoDB with Mongoose

Once we have the connection established, let’s dig into the Express MongoDB and Mongoose more. MongoDB is a non-relational database that stores the data in JSON-like documents. Fields can vary from a document to document, and the data structure can change over time.

The MongoDB database holds documents in collections. They are analogous to tables in relational databases. If you want to save data into the database using Mongoose, the first thing to do is to create a schema. It defines the shape of the documents within a collection.

Every key of our postSchema defines a property in our documents. There are multiple types that you can use. We explore them later, but if you want to check them out, look into the documentation.

The schema is just a definition of how should the document look like. To use it, you need to create a Model.

The first argument is the name of the collection that your model is for. Please, keep in mind that it is supposed to be singular. Mongoose automatically converts it to a plural version, and therefore a collection named posts is created.

Models

In this tutorial, I decided to follow the Model-view-controller pattern. The Express MongoDB part of the course is a proper time to introduce the next part of the MVC pattern: the model. It couples well with the idea of the model from Mongoose. The model is responsible for managing the data of the application and represents its structure. Let’s create a distinct file for its definition.

src/posts/posts.model.ts

Thanks to using  TypeScript is now aware of all the fields you defined in the interface and knows that it can expect them to be available in the post model.

Our model is now ready to be used!

Saving a document

To save a document in the database, you first need to create it. To do it, we use the model that we prepared above. An instance of a model is called a document.

The post that was created by using   is not yet saved into the database, but it already has a property called  . It is a unique id and is a combination of both a timestamp and a random string.

When you run   it is saved to the collection of posts. It returns a promise.

You can always look into the data in your database through the interface on mLab.

Express MongoDB mLab

Retrieving all documents

There are quite a few ways to retrieve documents. The most basic one is with the usage of the find function.

The find method returns a Query object. You can call the then function on it which causes the query to be executed with a promise.

The ability to call the then method on the Query can be a bit misleading, but remember that    does not return the promise itself. Please note that the find method does not cause the query to be executed, it happens after you call the then function. You can also do it by calling the exec function that returns a promise.

The find method can also accept an argument with conditions. For example,   would return only my posts. The course will cover this aspect later.

 returns all the posts.

Retrieve a certain document

There might often be a situation in which you only want to get one document. A way to do that is to use its unique id. You can pass it using additional route parameters.

The parameters are available to you in the   object. The function   finds a single document based on the id provided. The  function is similar to . You can call   . It also accepts other conditions.

We respond with a newly created document by calling  .

 returns the post with a given id, if found.

Replacing a document

Another common use of the API is when you intend to change an existing document. You can do so with the use of HTTP PATCH

The   function updates a document with a given id using the provided data. If we pass the   into the options, in the callback we have the new, modified document instead of the old one. We then send it back with  .

Similar to , you can also use   instead of  .

With HTTP PATCH you are supposed to only send the parameters you wish to change. If you send only some of the properties, the rest of them shouldn’t be modified. This is not the case with HTTP PUT that should replace the whole document with a new one. If you don’t provide the properties that you don’t wish to change, they should disappear.

 replaces properties of a post with a given id sent with the request body.

Removing a document

The way to remove an existing document is to use HTTP DELETE.

To perform the action, we use  . If the removal succeeds, we send back status   meaning that the task completed. If it fails, in this simple example we assume that the post wasn’t found and send back the  status.

 deletes a post with a given id if found.

The controller

Summing up all of the model manipulations, the controller now looks like that:

src/posts/posts.controller.ts

As you can see, I introduced the controller interface to make sure that all the controllers have certain properties.

src/interfaces/controller.interface.ts

Summary

In this article, we’ve covered all the basics of using Express MongoDB. It also included using environment variables so that it can be done in a clean and maintainable manner. We’ve learned how to list, create, modify and delete documents. To do that we used HTTP methods: GET, POST, PATCH and DELETE. Tune in for upcoming parts of the tutorial, because we have important matters to cover such as the error handling.

Series Navigation<< TypeScript Express tutorial #1. Middleware, routing, and controllersTypeScript Express tutorial #3. Error handling and validating incoming data >>
avatar
  Subscribe  
newest oldest most voted
Notify of
Predrag Stojadinovic

Just a hint for those who hate callback hell:

park joonmo
park joonmo

to any one who are strunggling with mongodb connection error ‘failed to connect to server on first connect …’

if you are doing this with public wifi like cafe wifi, check if you add your public ip in mongodb.atlas – security – network access – ip whitelist

you can find out your public ip in website like https://www.whatismyip.com

but whiltelisting public ip might not be a good idea if your database is not for personal purpose

Roman Durny
Roman Durny

Could anybody help me with this error ?

Missing environment variables:
MONGO_PASSWORD: undefined
MONGO_PATH: undefined
MONGO_USER: undefined
PORT: undefined

Here is my code http://leteckaposta.cz/136673781

Tristam2
Tristam2

The envalid library was not ported to typescript (I test @types/envalid). How to maintain this feature without using envalid or how to make envalid compatible with typescript?