Setting up Express Routes

Image for post
Image for post

A few weeks ago I wrote a post about designing Mongoose schemas for a MongoDB that I am porting from a Postgres API I built a few months back. The project, Cart Compass, is a virtual shopping cart that allows users to create lists and then dynamically reorder them in a way that will guide quickly through the supermarket aisles.

I spent last week setting up the routes in Express to see how well the nested Mongoose schemas function at delivering requested data from the database to the React client.

Initial Setup

Heading over to VSCode’s terminal I install Express 4.14.1. I also install the node.js CORS package, which enables cross-origin resource sharing between resources. By default, browsers won’t allow servers from the same origin to talk, the situation we find ourselves in when working locally in development mode with say, an Express server running serving the APIs and a React site all running at localhost. This behavior is for security, but won’t be an issue running things locally. Installing the CORS package allows us to by pass this restriction.

I also install nodemon which will restart the Express server every time a change is made in the code — a real time saver when developing!

Next I install the BCrypt and JSONWebToken packages. Bcrypt will be used to hash and later verify passwords before storing them in the MongoDB User collection. JSONWebToken will create and later verify an encrypted token that can be sent back to the client and stored in the browser’s local storage. The token allows a user’s authorization to persist after closing down and reopening the browser, a new session. As an aside, there’s a passionate discussion about the best way to store JWTs, or even to use them at all, but that’s out of the scope of this article.

Here’s the initial setup of an instance of Express Server (app). The code also imports in our configured instance of Mongoose which allows us to access and communicate with our MongoDB database.

// index.jsconst express = require('express');
const cors = require('cors');
require('./db/mongoose');
const app = express();
const port = process.env.PORT || 3000;
app.use(express.json());
app.use(cors());
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});

Here’s the project folder structure I use:

Image for post
Image for post

Express Router

I set up four different routers to keep the routers a manageable size. Each router is imported into index.js and added to the instance of the Express server:

// index.jsconst userRouter = require('./routers/user');
const listRouter = require('./routers/list');
const masterItemRouter = require('./routers/masterItems');
const categoriesRouter = require('./routers/categories');
...app.use(userRouter);
app.use(listRouter);
app.use(masterItemRouter);
app.use(categoriesRouter);

Here is a URL plan I came up that I found very helpful when writing the routers. It includes the endpoint’s Method/URL, the parameters expected, as well as the intended success and error returns. I found this especially helpful when designing requests in the React frontend.

Image for post
Image for post
Express Router plan

The actual routers are fairly simple to put together. Here’s the User router:

// src/routers/user.jsconst express = require('express');
const User = require('../models/user');
const auth = require('../middleware/authentication');
const router = new express.Router();/// routesmodule.exports = router;

We import express so we can create an instance of Router for the new router. We also need to import the User model to use when creating new Users. Auth is Express middleware (authentication.js) that checks if a token is present and verified. If so, the current user is returned. This allows us to easily setup authorized routes that are only accessible if a user is logged in.
Finally the router is exported.

Below I define a route that creates a new user. We add the HTTP method used by the specific route (POST in this case) and the router takes two arguments — the path and a callback (handler function). The callback has two or three arguments — req (request) and res(response), and optionally next which can be used to call multiple callbacks. The callback below checks if the password and confirmation match. If they do, the new user is created. GenerateAuthToken is Mongoose is a Mongoose schema method that generates the jwt, saves it in the User’s document and returns the jwt to the router to add to the response back to the client.

// src/routers/user.jsrouter.post('/users', async (req, res) => {
if (req.body.password !== req.body.passwordConfirmation) {
res.status(400).send('Passwords don\'t match. Please try again.');
}
const user = new User(req.body); try {
const token = await user.generateAuthToken();
res.status(200).send({ user, token });
} catch (e) {
if (e.code === 11000) {
res.status(400).send('Email already exists.');
} else {
res.status(500).send('Something went wrong.');
}
}
});

Tools of the trade

I used two apps regularly to verify that my routes were working as expected.

Robo3T is a MongoDB management tool. It allows us to view the structure and contents (the collections and documents) in our database. Since my design incorporated nested subdocuments, my User documents contained a lot of data (this is the beauty of MongoDB). Robo3T allows us to view the structure and verify things are getting stored according to how we’ve set up document schema.

Postman allows us to easily create, organize and test API requests. Requests can be saved to a project folder for reuse. In addition, environment variables can be saved such as common base URLS and authentication tokens. This minimizes the amount of set up that needs to be done with each new request type and makes switching to a production mode with a different URL a breeze. Spending some time with the documentation is time well spent, and MUCH EASIER than trying to test requests from your client.

Wrap up

This is my first experience setting up an original API based on MongoDB. While the unified, nested nature of MongoDB takes some getting used to after working with SQL databases, I really enjoyed being able to easily visualize my data. If I want some User’s data I find the User and the rest is inside. Being able to request a list of items from a User’s list just requires digging down into the nested JSON properties. Since MongoDB can store arrays, the list, once found and sent, is ready to be easily consumed by the client.

I need to give a shoutout to Andrew Mead’s excellent ‘The Complete Node.js Developer Course’ for giving me my initial exposure to Express and MongoDB. The course is informative as well as giving lots of opportunities to try out the concepts. I also spent quite a bit of time with Express, MongoDB and Mongoose documentation as well, trying to get a better understanding of the possibilities of these popular tools.

Written by

After years of teaching music in Austin, Shanghai and Yangon, I’m making a career change to my other passion, software development.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store