Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Chiranjib
Chiranjib

Posted on • Edited on

     

Setting up a Node.js backend

Previous:Learn to build with MERN stack

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 apackage.json file in your directory. You may want to read more aboutnpm,npmrc,package.json andpackage-lock.json here:About npm

To get our feet wet, let's install a package -winston

> npm install --save winston

Let's check our package.json

...."dependencies": {    "winston": "^3.8.2"}....
Enter fullscreen modeExit fullscreen mode

Notice the Caret (^) next to the version. This indicates winston should have any package compatible with3.8.2. In some cases, this may present an unwanted problem. If we runnpm 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 usingnpmrc for this.

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

save-exact=true
Enter fullscreen modeExit fullscreen mode

If you want to check all the options available for this file, check outnpmrc config options
Now, let's uninstall the package and install again

> npm uninstall --save winston> npm install --save winston
Enter fullscreen modeExit fullscreen mode

Notice the package.json has this now

...."dependencies": {    "winston": "3.8.2"}....
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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
Enter fullscreen modeExit fullscreen mode

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 fileconfig/index.js and put the following content in it:

module.exports = {    SERVER: {        PORT: 3000,        REQUEST_BODY_SIZE_LIMIT: '10mb'    },    LOGGING: {        LEVEL: 'warn'    }};
Enter fullscreen modeExit fullscreen mode

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 apostinstall 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"    }....
Enter fullscreen modeExit fullscreen mode

You may runnpm i after this, and notice this in the console output:

....> link-module-aliaslink-module-alias: _controllers -> ./controllers, _entities -> ./entities, _utils -> ./utils, _config -> ./config...
Enter fullscreen modeExit fullscreen mode

Now, we may use these as normal require statements likerequire('_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 likenode index.js for the sake of consistency. Let's inject the following scripts in thepackage.json

...."scripts": {        "test": "echo \"Error: no test specified\" && exit 1",        "start": "node index.js",        "dev": "nodemon index.js",        "postinstall": "link-module-alias"    }....
Enter fullscreen modeExit fullscreen mode

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 fileutils/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()    ]});
Enter fullscreen modeExit fullscreen mode

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}`);});
Enter fullscreen modeExit fullscreen mode

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 onlywarn level using an environment variable. The linelogger.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. Thenext() 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

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

Next:Create modular routes with express

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Been in tech since 2010. Been a full stack dev, DevOps, Engineering leader and lately a Platform Engineer
  • Location
    Singapore
  • Education
    B. Tech. from NIT Silchar, India
  • Work
    Platform Engineer
  • Joined

Trending onDEV CommunityHot

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp