Setting up an express application with controllers and routing
Published: 2022-07-25 10:49:03


Created: May 11, 2022 2:41 AM
Published: Yes

## Set Up

We initialise our node project in our directory of choice, create a server directory and a server.js file for our node server

```bash
npm init -y
mkdir server
touch server/server.js
```

We install `babel` and `webpack` dev-dependencies to bundle and minify our code for eventual production

```bash
npm install -D @babel/core @babel/preset-env @babel/preset-react babel-loader webpack webpack-cli webpack-node-externals
```

<aside>
💡 `webpack-node-externals` prevents us from having modules in our node_modules folder bundled into our code as this would make it bloated.  `Webpack` will load modules from the node_modules folder and bundle them in. This is fine for frontend code, but backend modules typically aren't prepared for this. Read more [here](https://www.npmjs.com/package/webpack-node-externals#why-is-not-bundling-node_modules-a-good-thing)

</aside>

We then configure `webpack` by creating a `webpack.config.js` at the root of our project directory

```jsx
const path = require('path');
const CURRENT_WORKING_DIR = process.cwd();
const nodeExternals = require('webpack-node-externals');

const config = {
    name: "server",
    entry: [ path.join(CURRENT_WORKING_DIR, '/server/server.js')],
    target: "node",
    output: {
        path: path.join(CURRENT_WORKING_DIR, '/dist/'),
        filename: "server.generated.js",
        publicPath: '/dist/',
        libraryTarget: "commonjs",
    },
    externals: [nodeExternals()],
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: ['babel-loader']
            }
        ]
    }
};

module.exports = config;
```

We configure babel by creating a `.babelrc` file in our project root

```jsx
{
  "presets": [
    ["@babel/preset-env",
      {
        "targets": {
          "node": "current"
        }
      }]
  ]
}
```

You can read more about code bundling with these resources:

[What is bundling with webpack?](https://www.dottedsquirrel.com/bundling-javascript/)

[Demystifying Code Bundling with JavaScript Modules](https://www.simplethread.com/javascript-modules-and-code-bundling-explained/)

Next we install express to begin building our server

```bash
npm install express
```

We will install the following packages as express middleware to give our server some enhanced functionality

[body-parser](https://www.npmjs.com/package/body-parser): For parsing the incoming request body as json and urlencoded values (also works for FormData). The request body would then be available for our handlers.

[cookie-parser](https://www.npmjs.com/package/cookie-parser): For parsing cookies in request headers and making them available in the request object for our handlers

[helmet](https://www.npmjs.com/package/helmet): For adding necessary security headers to our server to improve security. This is necessary for guarding against basic attacks like cross site scripting attacks. It is not the end enough on its own but is useful to provide some basic level of security

[compression](https://www.npmjs.com/package/compression): For compressing response bodies

[cors](https://www.npmjs.com/package/cors): For setting up our Cross Origin Resource Sharing policy to enable us clients from allowed domains to communicate with our server. A good write up on Cross Origin Requests can be viewed [here](https://javascript.info/fetch-crossorigin)

```bash
npm install body-parser cookie-parser helmet compression cors
```

We can then create our express app in an `express.js` file in the server directory

```jsx
import express from "express";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import helmet from "helmet";
import compress from "compression";
import cors from "cors";

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser());
app.use(helmet());
app.use(compress());
app.use(cors());

export default app;
```

We export our express app to be imported in our main `server.js` file. We a file config.js to hold necessary configurations to be used across our server and export a single config object to be imported in `server.js`

```jsx
export default {
    env: process.env.NODE_ENV || 'development',
    port: process.env.PORT || 4000
}
```

```jsx
import mongoose from "mongoose";
import config from './config/config'

app.listen(config.port, (err) => {
    if (err) {
        console.error(err);
    } else {
        console.info(`Application running on PORT: ${config.port}`)
    }
})
```

With our server files set up we can now start our dev server. Nodemon is a useful resource of running your dev server as it automatically restarts (kind of like hot reload) when you make a change to your source code

```bash
npm install -D nodemon
```

We then create a `nodemon` config file `nodemon.json` and set it up to run `webpack` and our generated code from the `dist` directory we indicated in `webpack.config.js`

```json
{
"verbose": false,
"watch": ["./server"],
"exec": "webpack --mode=development --config webpack.config.server.js && node ./dist/server.generated.js"
}
```

Finally we set up a dev script in our package.json to run nodemon with the above config to set up our run our dev server

```json
{
  "name": "social-media-app",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon", //. <-- HERE
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "body-parser": "^1.19.2",
    "compression": "^1.7.4",
    "cookie-parser": "^1.4.6",
    "cors": "^2.8.5",
    "express": "^4.17.3",
    "helmet": "^5.0.2",
  },
  "devDependencies": {
    "@babel/core": "^7.17.5",
    "babel-loader": "^8.2.3",
    "nodemon": "^2.0.15",
    "webpack": "^5.69.1",
    "webpack-cli": "^4.9.2",
    "webpack-node-externals": "^3.0.0"
  }
}
```

We can now run the dev script to start our server

```bash
npm run dev
```

The output below shows our code has been bundled by `webpack` and the generated file is currently being run and the server is running.

![Screenshot 2022-05-11 at 1.39.03 AM.png](Setting%20up%20an%20express%20application%20with%20controllers%20f212169bd7f4421da53c5ef513a42bdb/Screenshot_2022-05-11_at_1.39.03_AM.png)

Now we can begin the real work

### Users

Users are the first resource we are going to work with. We want to have the ability to create, read, update and delete users as well as have the foundation for basic authentication. 

We will use Prisma as the Object Relational Mapper for our MongoDB database. Mongoose is also another option but I like Prisma’s type safety when used with typescript and schema declaration as it makes maintenance easier.

The team at Prisma have a walkthrough for setting up Prisma with MongoDB and it can be found here:

[Start from scratch with MongoDB (15 min)](https://www.prisma.io/docs/getting-started/setup-prisma/start-from-scratch/mongodb-typescript-mongodb)

We can follow throw the steps to begin set up till schema setup. Below is the schema for our User Model

```jsx
type UsersImage {
  contentType String
  data        Bytes
}

model User {
  id              String      @id @default(auto()) @map("_id") @db.ObjectId
  v               Int         @map("__v") @default(0)
  createdAt       DateTime    @default(now()) @db.Date
  email           String      @unique
  password        String
  image           UsersImage?
  name            String
  salt            String
  updatedAt       DateTime    @updatedAt @db.Date
  about String?

  @@map("users")
}
```

We define a field name in the first column, field type in the next and field attributes and modifiers in the final column

- id: The primary key for each user document. This will be a string will be denoted as the primary key using the `@id`. The `@default(auto())` modifier auto generates an ID by default when a document is created. @map enables us to set the field name within the database. In MongoDB the underlying **`ID`** field name is always **`_id`** and must be mapped with **`@map("_id")`.** When we create documents and view them in the database we will see `_id` as a field and when we use the Prisma ORM in our development we would be able to access this as `id` on the user object. Prisma will take care of the mapping. @db.ObjectId is a native database type attribute that allows us to specify the data type for the field. ObjectIds are field types native to MongoDB documents similar to how SQL databases have types such as VARCHAR.
- v: This field on the document is for document versioning. In the future if we begin saving a different version of documents but do not want to discard the old data, this field would enable us to differentiate between the version of documents to use. You can read more about the [Document Versioning Pattern here](https://www.mongodb.com/blog/post/building-with-patterns-the-document-versioning-pattern). We set its default version to 0
- createdAt and updatedAt: We store the dates when documents are created and updated
- email: We store users email and ensure it is unique
- password: Users hashed password to be stored
- image: For now we will be storing a users image data as Binary Data in the database. Note that this is not best practice and later on we will work at migrating the schema to store links to the image to be fetched from remote storage. We create a `type` field to define shape of our image. We will store the data and the type of content (it’s MIME type)
- name: User’s name as string
- about: Short description of the user
- salt: This is a piece of random data used when we are encrypting the user’s plain text password during authentication. My having a different salt value for different users we increase our password security as it limits the effects of common passwords being exploited. Salting’s [Wikipedia page](https://en.wikipedia.org/wiki/Salt_(cryptography)) is a great place to get up to speed on the benefits of using a salt.

A full reference of Prisma’s fields and modifiers can be found [here](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#now)

After defining our model we can proceed to install our Prisma client

```bash
npm install @prisma/client
```

We then run `prisma generate` which reads our schema and generates a version of prisma client (with type defs and safety) tailored to our schema.

```bash
prisma generate
```

To enable use use prisma we need to initialise the prisma client which will allow us to query our database.

```bash
touch server/prisma/prisma.js
```

```bash
import {PrismaClient} from '@prisma/client'

const prisma = global.prisma || new PrismaClient()

if (process.env.NODE_ENV === 'development') {
    if (!global.prisma) {
        global.prisma = prisma
        global.prisma.$connect().then(() => {
            console.log('Database connected successfully')
        });
    }
} else {
    prisma.$connect().then(() => {
        console.log('Database connected successfully')
    });
}

export default prisma;
```

We import that PrismaClient connect to the database and export the prisma object for use in our app. During development is common to restart the server many times as we make changes hence to avoid creating multiple connections we attach the prisma object to the global node object (known as `globalThis`). We check if this object exists on the global object when the app starts or create a new client otherwise. Note the assignment to global is only done in development

With that done we can now begin querying the database. 

We want to make it possible to create, read, update and delete a user in our application. We will do this in a user controller using the Prisma client API to query the database. 

```bash
touch server/controllers/user.controller.js
```

Let’s define a method to create and store users in the database. This would be useful during the sign up process

```jsx
import crypto from 'crypto';
import isEmail from 'validator/lib/isEmail';
import contains from 'validator/lib/contains';
import prisma from '../prisma/prisma';
import {ValidationError} from "../helpers/db.error.handler";

const encryptPassword = (password, salt) => {
    try {
        return crypto.createHmac('sha1', salt).update(password).digest('hex');
    } catch (err) {
        console.error(err);
    }
}

const makeSalt = () => String(Math.round((new Date().valueOf() * Math.random())))

const emailIsValid = (email) => {
    return isEmail(email)
}

const nameIsValid = (name) => {
    return name.length > 1
}

const passwordIsValid = (password) => {
    const errors = [];
    if (!password) {
        errors.push('Password is required')
    }
    if (password?.length < 8) {
        errors.push('Password must be at least 8 characters')
    }
    if (password && contains(password, 'password')) {
        errors.push('Password should not contain the word password')
    }

    if (errors.length > 0) {
        return {
            isValid: false,
            message: errors.join(',')
        }
    } else {
        return {
            isValid: true
        }
    }
}

const store = async (req, res, next) => {
    const {name, email, password} = req.body;
    const userData = {name, email, password};
    try {
				const errors = {};

		    if (!nameIsValid(name)) {
		        errors.name = 'User name too short'
		    }
		
		    if (!emailIsValid(email)) {
		        errors.email = 'Invalid email'
		    }

				const existingUser = await prisma.findUnique({
	        where: {
	            email
	        }
		    })

		    if (existingUser !== null) {
			     throw new ValidationError({
            error: "User already exists"
	        })
		    }
			
		    const passwordValid = passwordIsValid(password)
		
		    if (!passwordValid.isValid) {
		        errors.password = passwordValid.message
		    }
		
		    if (Object.keys(errors).length > 0) {
		        throw new ValidationError(errors);
		    }
		
		    const salt = makeSalt();
		
		    const hashed_password = encryptPassword(password, salt);
		
		    await prisma.create({
		        data: {
		            name,
		            email,
		            password: hashed_password,
		            salt,
		        }
		    })
        return res.status(200).json({
            message: 'User saved successfully'
        })
    } catch (e) {
        if (e instanceof ValidationError) {
            return res.status(422).json(e.errors)
        }
        next(e);
    }
};

export default { store }
```

Let’s break the above down. We have created a method `store`, which is [Express middleware](https://expressjs.com/en/guide/writing-middleware.html), to handle the creation of users when a request is sent to this route. It takes, the incoming request object as a parameter as well as an object for dispatching a response. The `next` function is useful if we intend on passing an error to an error handler or relinquishing control to the next middleware. In this case we will be using next to send any unexpected errors to our error handler. You can learn all about how express works from its [documentation](https://expressjs.com/en/starter/installing.html)

We destructure the required keys from the request body and begin validation. We create separate functions to handle validation of our data. We create an error object that will be used to hold the validation errors if the request data does not pass our validation.

`nameIsValid` checks that there is more than a single character for the users name

`emailIsValid` uses the helper method `isEmail` from the [validator](https://www.npmjs.com/package/validator) package to assert that the provided email is a valid email format

We also check if the user already exists and throw and error immediately if this is the case

`passwordIsValid` checks that the password is present, is at least 8 characters and does not contain the word “password”.  We store the individual error messages in an array and concatenate and return this as the message property of the output object as well as `isValid` to denote whether the password is valid

If any of these validation checks are failed, the field names are added to the error object. Based on the number of keys on the error object after validation we throw a ValidationError with the errors as part of the constructor. We create the ValidationError as shown below 

```bash
touch server/helpers/db.error.handler.js
```

```jsx
export class ValidationError extends Error {
    constructor(errors) {
        super();
        this.errors = errors;
    }
}
```

We check for this error in the `catch` block of our `try catch` and based on that send a 422 status code and an object of errors as a response

After all the data is validated, we create a salt using `makeSalt` which simply generates a random set of numbers based on the current date in milliseconds. We use this to encrypt the users password using node’s `crypto` package.

Finally we use the prisma client to create the user and send a 200 response with a message back to the user. If an error occurs that is not a ValidationError we forward this to our error handler user `next(e)` We will cover this error handler soon. Finally we export the store method as part of an object default export. This will allow other modules to have access to it.   

Routing

Now that we have our controller that would handle the creation of users we need to make it possible to receive data to create users.  We do this by hooking up our controllers to routes for our express server to use. 

```jsx
import express from 'express';
import userController from '../controllers/user.controller';

const userRouter = express.Router();

userRouter.route('/api/users')
	.post(userController.store)

export default userRouter
```

Above we:

1. Import the `express` module and the exported `userController` object
2. Create an `express` router to handle routing
3. Route `POST` requests to `/api/users` and handle them with the `store` method we created
4. Export the `userRouter` 

Our final step is to “attach” our userRouter to our server

```jsx
import express from "express";
import bodyParser from "body-parser";
import cookieParser from "cookie-parser";
import helmet from "helmet";
import compress from "compression";
import cors from "cors";
import userRouter from "./routes/user.routes"; //new

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true}));
app.use(cookieParser());
app.use(helmet());
app.use(compress());
app.use(cors());
app.use(userRouter); //new

export default app;
```

With our router hooked up to our server we can spin up our server to begin receiving requests

```bash
npm run dev
```

Using post man we make a `POST` request to our server (running on port 4000 remember) at the route `/api/users` to create a user

![Screenshot 2022-06-02 at 12.05.23 AM.png](Setting%20up%20an%20express%20application%20with%20controllers%20f212169bd7f4421da53c5ef513a42bdb/Screenshot_2022-06-02_at_12.05.23_AM.png)

We get a successful response from the server

![Screenshot 2022-06-02 at 12.15.47 AM.png](Setting%20up%20an%20express%20application%20with%20controllers%20f212169bd7f4421da53c5ef513a42bdb/Screenshot_2022-06-02_at_12.15.47_AM.png)

We can check our database to see the user created successfully

![Screenshot 2022-06-02 at 12.18.38 AM.png](Setting%20up%20an%20express%20application%20with%20controllers%20f212169bd7f4421da53c5ef513a42bdb/Screenshot_2022-06-02_at_12.18.38_AM.png)

We have successfully set up a simple express application, created a controller to handle logic and set up routes to direct requests to our controllers. On our controller we put all the methods directly into the handler but its useful to encapsulate the logic in a service or helper function that insulates the handler from changes that are at a lower level of abstraction. That way we can simple call a service to create a user and all the logic would be contained there thereby ensuring our controller is clean and operates at an appropriate level of abstraction,

Listing, deleting and updating are all further functionalities we can incorporate into our server by creating more services, controllers and routes as well as sending the appropriate queries to our Mongo database using our Prisma Client. The Prisma docs are full of details on how to do these and you can check these out to expand the set up and create a simple CRUD API