Setting up a Node.js backend
Tags: node, webdev, javascript
Published: 2023-02-14 14:46:48

**<u>_Previous:_</u>** [Learn to build with MERN stack](https://dev.to/chiranjib_b/learn-to-build-with-mern-stack-15a9)

> DISCLAIMER: This guide by no means aspires to be the holy grail. At the time of writing this I've been developing software for more than a decade and let's say I know of a few things that would make coding easy when it comes to basic principles like Readability, Maintainability, etc.
P.S. If you don't like my suggestions, please don't hurl abuses at me, I have my friends for doing that. If you have some constructive feedback, please do share!

#### Step 1 - start the project and install a package

Pick a folder on your device where you'd like to keep your code. It can be surprisingly easy to start a Node.js project. Open command terminal and navigate to the directory of your choice. Once there, execute this:

`> npm init`

Once you fill out the questionnaire, you should notice a _package.json_ file in your directory. You may want to read more about _npm_, _npmrc_, _package.json_ and _package-lock.json_ here: [About npm](https://docs.npmjs.com/about-npm)

To get our feet wet, let's install a package - [winston](https://www.npmjs.com/package/winston)

`> npm install --save winston`

Let's check our package.json

```
....
"dependencies": {
    "winston": "^3.8.2"
}
....
```

Notice the Caret (^) next to the version. This indicates winston should have any package compatible with `3.8.2`. In some cases, this may present an unwanted problem. If we run `npm update`, it can potentially upgrade the package to the next compatible version. I like to exercise a bit more control on the package and the versions I have. There are many ways to do this, I prefer using _npmrc_ for this.

Let's create a file named `.npmrc` as a sibling of `package.json` and put this line in it:

```
save-exact=true
```

If you want to check all the options available for this file, check out [npmrc config options](https://docs.npmjs.com/cli/v9/using-npm/config)
Now, let's uninstall the package and install again

```
> npm uninstall --save winston
> npm install --save winston
```

Notice the package.json has this now

```
....
"dependencies": {
    "winston": "3.8.2"
}
....
```
Sweet! We have a solid grip on all the package versions.


####Step 2 - installing a few essentials

```
> npm install --save express
> npm install --save-dev nodemon
> npm install --save-dev eslint
```

If you notice, we've installed express with `--save` and the other two with `--save-dev`.
The idea is to have some packages that are only required in our local setup while writing code. When it's time to release to production, the final version can be leaner to minimize the size of the shipment (more on that later). 

About the packages:
- express : popular library to define endpoints
- nodemon : popular for development as it watches files for changes and restarts the node process
- eslint : library required for enforcing code style guidelines

####Step 3 - set up linting

It's essential to have a few rules to keep the code style consistent, eslint helps us do that. Let's set it up, run this command

`npx eslint --init`

Answer the questions, specify your style of coding, and we're good to go. You may observe, a `.eslintrc.js` file would have gotten created (provided you picked JavaScript as your option instead of JSON or YAML)

####Step 4 - create a few folders

For the sake of sanity and keeping things modular, let's create the following directory structure to begin with. Don't worry about the content yet, we will focus on this as we move along.

```
/
  - config
  - controllers
  - entities
  - utils
```

####Step 5 - preparing for env variables

It's a good practice to put all the variables in a single location, so that it's easier to refactor in the future. This may seem an overkill when we are just starting out, but as the application code grows and complexity increases, environment variables get messy and making changes can get tricky.
Let's create a file `config/index.js` and put the following content in it:

```
module.exports = {
    SERVER: {
        PORT: 3000,
        REQUEST_BODY_SIZE_LIMIT: '10mb'
    },
    LOGGING: {
        LEVEL: 'warn'
    }
};
```

####Step 6 - setting up project for absolute path

One thing that quickly gets ugly, are relative file paths (require `../../../folder1/folder2/file1.js`). Nowadays, there are many ways to get rid of it, here we'll explore one option.

`npm i --save link-module-alias`

Edit the package.json to add a `postinstall` script and the `_moduleAliases` section as shown below:

```
...
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "postinstall": "link-module-alias"
    },
    "_moduleAliases": {
        "_controllers": "./controllers",
        "_entities": "./entities",
        "_utils": "./utils",
        "_config": "./config"
    }
....
```

You may run `npm i` after this, and notice this in the console output:

```
....
> link-module-alias

link-module-alias: _controllers -> ./controllers, _entities -> ./entities, _utils -> ./utils, _config -> ./config
...
```

Now, we may use these as normal require statements like `require('_controllers')` in any file within our project structure.

In this post, I am only trying to highlight that you SHOULD enable something like this, by no means am I preaching that you use the same package.

####Step 7 - specify start scripts

We choose to run Node.js projects with npm commands instead of a simple command like `node index.js` for the sake of consistency. Let's inject the following scripts in the `package.json`

```
....
"scripts": {
        "test": "echo \"Error: no test specified\" && exit 1",
        "start": "node index.js",
        "dev": "nodemon index.js",
        "postinstall": "link-module-alias"
    }
....
```
####Step 8 - setting up the first endpoint

Let's try to set up a healthcheck endpoint that simply responds with text "OK". First, let's enable us to use the logger library we integrated earlier. Create a file `utils/logger.js` as shown below:

```
const winston = require('winston');
const { LOGGING: { LEVEL } } = require('_config');

const { combine, timestamp, prettyPrint, errors } = winston.format;
    
module.exports = winston.createLogger({
    format: combine(
        errors({ stack: true }),
        timestamp(),
        prettyPrint()
    ),
    level: LEVEL,
    transports: [
        new winston.transports.Console()
    ]
});

```

Now, we can focus on our route. Let's edit the index.js file as

```
const { SERVER: { PORT } } = require('_config');
const logger = require('_utils/logger');

const express = require('express');

const app = express();

app.use((req, res, next) => {
    logger.log('info', `Received request ${req.originalUrl}`);
    next();
});

app.get('/healthcheck', (req, res, next) => {
    res.send('OK');
});
    
app.use('/', (req, res) => {
    res.send(`${req.originalUrl} can not be served`);
});

app.listen(PORT, () => {
    logger.log('info', `Listening on port ${PORT}`);
});

```

Few highlights of the simple snippet above:
- all the statements defined for app, get executed in sequence
- we have defined a logger with winston that logs to the console for the sake of this example and restricted it to only `warn` level using an environment variable. The line `logger.log('info'...` won't print anything to the console until the level is set to `'info'`. This comes in handy when in production we want to start printing more log statements to debug a problem. We can just change the environment variable on the fly.
- `app.use()` statement in the beginning takes in a middleware that gets executed for any endpoint call that the app receives. This is useful for doing tasks like authentication, logging for audit, etc. The `next()` call is crucial here to pass on the request chain to the next applicable middleware or endpoint definition as applicable
- `app.get('/healthcheck')` is the endpoint we intended to define, which simply responds with 'OK'
- `app.use('/')` is the fallback endpoint, that would catch all the requests that could not be served, and we can show graceful error messages if we desire

To run the server, you need to execute either of the commands
- `npm run start`
- `npm run dev`

Voilà! We're all set! You may hit the endpoint we have just defined, and revel in the glory of your hard work.


![Healthcheck](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vb25s0v7vlma3yky758n.png)

So, we have our boilerplate ready. Let's shape it up to be more robust and introduce modularity for more routes.

**<u>_Next:_</u>** [Create modular routes with express](https://dev.to/chiranjib_b/create-modular-routes-with-express-5cp9)