---
title: "Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example"
description: "Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example."
author: "Mohammad Abu Mattar"
canonical: https://mkabumattar.com/blog/post/setting-up-node-js-express-mongodb-prettier-eslint-and-husky-application-with-babel-and-authentication-as-an-example
---

# Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example

## Introduction

All code from this tutorial as a complete package is available in this [repository](https://github.com/MKAbuMattar/template-express). If you find this tutorial helpful, please share it with your friends and colleagues, and make sure to star the repository.

Since the ECMAScript JavaScript Standard is revised annually, it is a good idea to update our code as well.

The most recent JavaScript standards are occasionally incompatible with the browser. Something like to babel, which is nothing more than a JavaScript transpiler, is what we need to fix this sort of issue.

So, in this little tutorial, I'll explain how to set up babel for a basic NodeJS Express application so that we may utilize the most recent ES6 syntax in it then layer on MongoDB, a Winston logger, Prettier, ESLint, Husky git hooks, and JWT authentication.

  By the end you'll have a production-ready Express API scaffold: ES module
  syntax via Babel, MongoDB through Mongoose, structured logging, enforced
  formatting and linting, git hooks that block bad commits, and a complete
  register/login flow secured with JSON Web Tokens.

You'll push the finished project to a remote over SSH, so if you haven't set up your keys yet, see [Git SSH Keys for GitHub, GitLab, and Bitbucket on Linux](/blog/post/git-ssh-keys-for-github-gitlab-and-bitbucket-on-linux).

## What is Babel?

Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments. Here are the main things Babel can do for you:

- Transform syntax
- Polyfill features that are missing in your target environment (through a third-party polyfill such as core-js)
- Source code transformations (codemods)

## Project Setup

We'll begin by creating a new directory called `backend-template` and then we'll create a new `package.json` file. We're going to be using pnpm for this example, but you could just as easily use npm or yarn if you prefer pnpm is fast and disk-efficient thanks to its content-addressable store.

```shell title="Create the project directory"
mkdir backend-template
cd backend-template
pnpm init
```

### Engine Locking

The same Node engine and package management that we use should be available to all developers working on this project. We create two new files in order to achieve that:

- `.nvmrc`: Will disclose to other project users the Node version that is being utilized.
- `.npmrc`: reveals to other project users the package manager being used.

```shell title="Create the .nvmrc files"
touch .nvmrc
echo "lts/fermium" > .nvmrc
```

```shell title="Create the .npmrc files"
touch .npmrc
echo 'engine-strict=true\r\nsave-exact = true\r\ntag-version-prefix=""\r\nstrict-peer-dependencies = false\r\nauto-install-peers = true\r\nlockfile = true' > .npmrc
```

With pnpm, repo-wide install settings live in a `pnpm-workspace.yaml` file at the project root something like this:

```yaml title="pnpm-workspace.yaml"
# Only run install/build scripts for packages you trust to build native binaries.
onlyBuiltDependencies:
  - esbuild

# Pin a single resolution for a dependency across the whole tree.
overrides:
  # 'some-transitive-pkg': 1.2.3

# Optionally defer adopting brand-new releases (supply-chain safety).
# minimumReleaseAge: 1440
```

Notably, the usage of `engine-strict` said nothing about `pnpm` in particular; we handle that in `packages.json`:

open `packages.json` add the `engines`:

```json title="packages.json"
{
  "name": "tutorial",
  "version": "0.0.0",
  "description": "",
  "keywords": [],
  "main": "index.js",
  "license": "MIT",
  "author": {
    "name": "Mohammad Abu Mattar",
    "email": "mohammad.abumattar@outlook.com",
    "url": "https://mkabumattar.github.io/"
  },
  "homepage": "YOUR_GIT_REPO_URL#readme",
  "repository": {
    "type": "git",
    "url": "git+YOUR_GIT_REPO_URL.git"
  },
  "bugs": {
    "url": "YOUR_GIT_REPO_URL/issues"
  },
  "engines": {
    "node": ">=14.0.0",
    "pnpm": ">=8.0.0"
  }
}
```

### Babel Setup

For the production build we install two main Babel packages.

- _`@babel/core`_: The primary package for running any Babel setup or configuration.
- _`@babel/preset-env`_: Gives us access to modern JavaScript features and transpiles them down to a version the target Node.js understands.

Babel is mostly used in the code base to take advantage of new JavaScript capabilities. Unless the code is pure JavaScript, we don't know if the server's Node.js will comprehend the specific code or not so transpiling before deployment is advised, and that's exactly what `@babel/cli` does in the `build` script.

  For development we don't want to transpile to disk on every save. Instead of
  `nodemon` + `babel-node`, we'll use [tsx](https://www.npmjs.com/package/tsx)
  a tiny Node.js runner powered by esbuild that executes modern ES module `.js`
  (and TypeScript) directly and ships a `--watch` mode, so it replaces **both**
  `nodemon` and `babel-node` with one dependency.

Development Setup:

```shell title="Install tsx for development"
pnpm add -D tsx
```

```shell title="Install the babel packages"
pnpm add express mongoose cors dotenv @babel/core @babel/preset-env

##  compression cookie-parser core-js crypto-js helmet jsonwebtoken lodash regenerator-runtime
```

```shell title="Install the babel packages"
pnpm add -D @babel/cli babel-plugin-module-resolver
```

Here, we initialize the package.json and install the basic Express server, mongoose, cors, dotenv, `@babel/core` and `@babel/preset-env` (for the production build), plus the dev tooling: `tsx`, `@babel/cli`, and `babel-plugin-module-resolver`.

#### Babel Configration

After that, we need to create a file called `.babelrc` in the project's root directory, and we paste the following block of code there.

```shell title="Create the .babelrc file"
touch .babelrc
```

```json title=".babelrc"
{
  "presets": ["@babel/preset-env"]
}
```

### Code Formatting and Quality Tools

We will be using two tools in order to establish a standard that will be utilized by all project participants to maintain consistency in the coding style and the use of fundamental best practices:

- [Prettier](https://prettier.io/): A tool that will help us to format our code consistently.
- [ESLint](https://eslint.org/): A tool that will help us to enforce a consistent coding style.

#### Prettier

Prettier will handle the automated file formatting for us. Add it to the project right now.

It's only needed during development, so I'll add it as a `devDependency` with `-D`

```shell title="Install Prettier"
pnpm add -D prettier
```

  Install the [Prettier VS Code
  extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
  so the editor formats on save instead of you running the CLI by hand. Keep
  Prettier in the project's dependencies anyway VS Code uses your project's
  local config and version.

We'll create two files in the root:

- `.prettierrc`: This file will contain the configuration for prettier.
- `.prettierignore`: This file will contain the list of files that should be ignored by prettier.

```json title=".prettierrc"
{
  "trailingComma": "all",
  "printWidth": 80,
  "tabWidth": 2,
  "useTabs": false,
  "semi": false,
  "singleQuote": true
}
```

```shell title=".prettierignore"
node_modules
build
```

I've listed the folders in that file that I don't want Prettier to waste any time working on. If you'd want to disregard specific file types in groups, you may also use patterns like \*.html.

Now we add a new script to `package.json` so we can run Prettier:

```json title="package.json" collapse={1-6, 8-13, 18-66}
{
  "name": "tutorial",
  "version": "0.0.0",
  "description": "",
  "keywords": [],
  "main": "index.js",
  "scripts": {
    "start": "node build/bin/www.js",
    "dev": "tsx watch src/bin/www.js",
    "clean": "rm -rf build",
    "build": "pnpm clean && pnpm exec babel src -d build --minified --presets @babel/preset-env",
    "lint": "eslint \"src/**/*.js\" --fix",
    "lint:check": "eslint \"src/**/*.js\"",
    "prettier": "prettier --write \"src/**/*.js\"",
    "prettier:check": "prettier --check \"src/**/*.js\"",
    "prepare": "husky install"
  },
  "license": "MIT",
  "author": {
    "name": "YOUR_NAME",
    "email": "YOUR_EMAIL",
    "url": "YOUR_WEBSITE"
  },
  "homepage": "YOUR_GIT_REPO_URL#readme",
  "repository": {
    "type": "git",
    "url": "git+YOUR_GIT_REPO_URL.git"
  },
  "bugs": {
    "url": "YOUR_GIT_REPO_URL/issues"
  },
  "engines": {
    "node": ">=14.0.0",
    "pnpm": ">=8.0.0"
  },
  "dependencies": {
    "@babel/core": "7.18.5",
    "@babel/preset-env": "7.18.2",
    "compression": "1.7.4",
    "cookie-parser": "1.4.6",
    "core-js": "3.23.2",
    "cors": "2.8.5",
    "crypto-js": "4.1.1",
    "dotenv": "16.0.1",
    "express": "4.18.1",
    "helmet": "5.1.0",
    "husky": "8.0.1",
    "jsonwebtoken": "9.0.0",
    "mongoose": "6.11.3",
    "regenerator-runtime": "0.13.9",
    "winston": "3.8.0"
  },
  "devDependencies": {
    "@babel/cli": "7.17.10",
    "@commitlint/cli": "17.0.3",
    "@commitlint/config-conventional": "17.0.3",
    "babel-plugin-module-resolver": "4.1.0",
    "eslint": "8.18.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-prettier": "4.0.0",
    "prettier": "2.7.1",
    "tsx": "4.19.2"
  }
}
```

You can now run `pnpm prettier` to format all files in the project, or `pnpm prettier:check` to check if all files are formatted correctly.

```shell title="Run Prettier"
pnpm prettier:check
pnpm prettier
```

to automatically format, repair, and save all files in your project that you haven't ignored. My formatter updated around 7 files by default. The source control tab on the left of VS Code has a list of altered files where you may find them.

#### ESLint

We'll begin with ESLint, which is a tool that will help us to enforce a consistent coding style, at first need to install the dependencies.

```shell title="Install ESLint"
pnpm add -D eslint eslint-config-airbnb-base eslint-config-prettier eslint-plugin-import eslint-plugin-prettier
```

We'll create two files in the root:

- `.eslintrc`: This file will contain the configuration for ESLint.
- `.eslintignore`: This file will contain the list of files that should be ignored by ESLint.

```json title=".eslintrc"
{
  "extends": [
    "airbnb-base",
    "plugin:prettier/recommended",
    "plugin:import/errors",
    "plugin:import/warnings"
  ],
  "plugins": ["prettier"],
  "rules": {
    "prettier/prettier": "error",
    "import/no-named-as-default": "off",
    "no-underscore-dangle": "off"
  }
}
```

```shell title=".eslintignore"
node_modules
build
```

Now we add a new script to `package.json` so we can run ESLint:

```json title="package.json" collapse={1-6, 8-11, 14-16, 18-66}
{
  "name": "tutorial",
  "version": "0.0.0",
  "description": "",
  "keywords": [],
  "main": "index.js",
  "scripts": {
    "start": "node build/bin/www.js",
    "dev": "tsx watch src/bin/www.js",
    "clean": "rm -rf build",
    "build": "pnpm clean && pnpm exec babel src -d build --minified --presets @babel/preset-env",
    "lint": "eslint \"src/**/*.js\" --fix",
    "lint:check": "eslint \"src/**/*.js\"",
    "prettier": "prettier --write \"src/**/*.js\"",
    "prettier:check": "prettier --check \"src/**/*.js\"",
    "prepare": "husky install"
  },
  "license": "MIT",
  "author": {
    "name": "YOUR_NAME",
    "email": "YOUR_EMAIL",
    "url": "YOUR_WEBSITE"
  },
  "homepage": "YOUR_GIT_REPO_URL#readme",
  "repository": {
    "type": "git",
    "url": "git+YOUR_GIT_REPO_URL.git"
  },
  "bugs": {
    "url": "YOUR_GIT_REPO_URL/issues"
  },
  "engines": {
    "node": ">=14.0.0",
    "pnpm": ">=8.0.0"
  },
  "dependencies": {
    "@babel/core": "7.18.5",
    "@babel/preset-env": "7.18.2",
    "compression": "1.7.4",
    "cookie-parser": "1.4.6",
    "core-js": "3.23.2",
    "cors": "2.8.5",
    "crypto-js": "4.1.1",
    "dotenv": "16.0.1",
    "express": "4.18.1",
    "helmet": "5.1.0",
    "husky": "8.0.1",
    "jsonwebtoken": "9.0.0",
    "mongoose": "6.11.3",
    "regenerator-runtime": "0.13.9",
    "winston": "3.8.0"
  },
  "devDependencies": {
    "@babel/cli": "7.17.10",
    "@commitlint/cli": "17.0.3",
    "@commitlint/config-conventional": "17.0.3",
    "babel-plugin-module-resolver": "4.1.0",
    "eslint": "8.18.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-prettier": "4.0.0",
    "prettier": "2.7.1",
    "tsx": "4.19.2"
  }
}
```

You can test out your config by running:

You can now run `pnpm lint` to format all files in the project, or `pnpm lint:check` to check if all files are formatted correctly.

```shell title="Run ESLint"
pnpm lint:check
pnpm lint
```

### Setup logger for development

I had a problem setting up the logger when constructing a server-side application based on Node and Express router. Conditions for the answer:

- Logging application event
- Ability to specify multiple logs level
- Logging of HTTP requests
- Ability to write logs into a different source (console and file)

I found two possible solutions:

- [Morgan](https://www.npmjs.com/package/morgan): HTTP logging middleware for express. It provides the ability to log incoming requests by specifying the formatting of log instance based on different request related information.
- [Winston](https://www.npmjs.com/package/winston): Multiple types of transports are supported by a lightweight yet effective logging library. Because I want to simultaneously log events into a file and a terminal, this practical feature is crucial for me.

I'll use Winston for the logging, first I'll install the package:

```shell title="Install Winston"
pnpm add winston
```

Then I'll create a file called `utils/logger.util.js` in it:

```shell title="Create the logger.util.js file"
touch src/utils/logger.util.js
```

```js title="src/utils/logger.util.js"

const levels = {
  error: 0,
  warn: 1,
  info: 2,
  http: 3,
  debug: 4,
};

const level = () => {
  const env = NODE_ENV || 'development';
  const isDevelopment = env === 'development';
  return isDevelopment ? 'debug' : 'warn';
};

const colors = {
  error: 'red',
  warn: 'yellow',
  info: 'green',
  http: 'magenta',
  debug: 'white',
};

winston.addColors(colors);

const format = winston.format.combine(
  winston.format.timestamp({
    format: 'YYYY-MM-DD HH:mm:ss:ms',
  }),
  winston.format.colorize({all: true}),
  winston.format.printf(
    (info) => `${info.timestamp} ${info.level}: ${info.message}`,
  ),
);

const transports = [
  new winston.transports.Console(),
  new winston.transports.File({
    filename: 'logs/error.log',
    level: 'error',
  }),
  new winston.transports.File({filename: 'logs/all.log'}),
];

const logger = winston.createLogger({
  level: level(),
  levels,
  format,
  transports,
});

```

### Build file structure and basic express application

at first we'll create a directory called `src`, and we'll build the file structure inside of it.

```shell title="Create the src directory"
mkdir src
cd src

mkdir bin config constants controllers env middlewares models repositories routers schemas security services validations

```

This is the directory layout we're building inside `src`:

- src/
  - bin/ server bootstrap (www.js)
  - config/ database connection
  - constants/ shared constants (HTTP codes, messages, paths)
  - controllers/ request handlers
  - env/ environment-variable loading
  - middlewares/ auth and error middleware
  - models/ Mongoose models
  - repositories/ data-access layer
  - routers/ route definitions
  - schemas/ Mongoose schemas
  - security/ hashing and token helpers
  - services/ business logic
  - validations/ request validation

for robust and don't repeat some text we'll create constants files in the `constants` directory:

```shell title="Create the constants directory"
touch src/constants/api.constant.js src/constants/dateformat.constant.js src/constants/http.code.constant.js src/constants/http.reason.constant.js src/constants/message.constant.js src/constants/model.constant.js src/constants/number.constant.js src/constants/path.constant.js src/constants/regex.constant.js
```

there are some constants that are used in the application, but that are not related to the application itself. For example, the constants that are used in the API are in the `api.constant.js` file, and there is a file that provides freely for HTTP codes and replies, but we'll make one from start.

```js title="src/constants/dateformat.constant.js"

  YYYY_MM_DD_HH_MM_SS_MS,
};
```

```js title="src/constants/http.code.constant.js"

  CONTINUE,
  SWITCHING_PROTOCOLS,
  PROCESSING,
  OK,
  CREATED,
  ACCEPTED,
  NON_AUTHORITATIVE_INFORMATION,
  NO_CONTENT,
  RESET_CONTENT,
  PARTIAL_CONTENT,
  MULTI_STATUS,
  ALREADY_REPORTED,
  IM_USED,
  MULTIPLE_CHOICES,
  MOVED_PERMANENTLY,
  MOVED_TEMPORARILY,
  SEE_OTHER,
  NOT_MODIFIED,
  USE_PROXY,
  SWITCH_PROXY,
  TEMPORARY_REDIRECT,
  BAD_REQUEST,
  UNAUTHORIZED,
  PAYMENT_REQUIRED,
  FORBIDDEN,
  NOT_FOUND,
  METHOD_NOT_ALLOWED,
  NOT_ACCEPTABLE,
  PROXY_AUTHENTICATION_REQUIRED,
  REQUEST_TIMEOUT,
  CONFLICT,
  GONE,
  LENGTH_REQUIRED,
  PRECONDITION_FAILED,
  PAYLOAD_TOO_LARGE,
  REQUEST_URI_TOO_LONG,
  UNSUPPORTED_MEDIA_TYPE,
  REQUESTED_RANGE_NOT_SATISFIABLE,
  EXPECTATION_FAILED,
  IM_A_TEAPOT,
  METHOD_FAILURE,
  MISDIRECTED_REQUEST,
  UNPROCESSABLE_ENTITY,
  LOCKED,
  FAILED_DEPENDENCY,
  UPGRADE_REQUIRED,
  PRECONDITION_REQUIRED,
  TOO_MANY_REQUESTS,
  REQUEST_HEADER_FIELDS_TOO_LARGE,
  UNAVAILABLE_FOR_LEGAL_REASONS,
  INTERNAL_SERVER_ERROR,
  NOT_IMPLEMENTED,
  BAD_GATEWAY,
  SERVICE_UNAVAILABLE,
  GATEWAY_TIMEOUT,
  HTTP_VERSION_NOT_SUPPORTED,
  VARIANT_ALSO_NEGOTIATES,
  INSUFFICIENT_STORAGE,
  LOOP_DETECTED,
  NOT_EXTENDED,
  NETWORK_AUTHENTICATION_REQUIRED,
  NETWORK_CONNECT_TIMEOUT_ERROR,
};
```

```js title="src/constants/http.reason.constant.js"

  'Requested Range Not Satisfiable';

  'Request Header Fields Too Large';

  'Network Authentication Required';

  CONTINUE,
  SWITCHING_PROTOCOLS,
  PROCESSING,
  OK,
  CREATED,
  ACCEPTED,
  NON_AUTHORITATIVE_INFORMATION,
  NO_CONTENT,
  RESET_CONTENT,
  PARTIAL_CONTENT,
  MULTI_STATUS,
  ALREADY_REPORTED,
  IM_USED,
  MULTIPLE_CHOICES,
  MOVED_PERMANENTLY,
  MOVED_TEMPORARILY,
  SEE_OTHER,
  NOT_MODIFIED,
  USE_PROXY,
  SWITCH_PROXY,
  TEMPORARY_REDIRECT,
  BAD_REQUEST,
  UNAUTHORIZED,
  PAYMENT_REQUIRED,
  FORBIDDEN,
  NOT_FOUND,
  METHOD_NOT_ALLOWED,
  NOT_ACCEPTABLE,
  PROXY_AUTHENTICATION_REQUIRED,
  REQUEST_TIMEOUT,
  CONFLICT,
  GONE,
  LENGTH_REQUIRED,
  PRECONDITION_FAILED,
  PAYLOAD_TOO_LARGE,
  REQUEST_URI_TOO_LONG,
  UNSUPPORTED_MEDIA_TYPE,
  REQUESTED_RANGE_NOT_SATISFIABLE,
  EXPECTATION_FAILED,
  IM_A_TEAPOT,
  METHOD_FAILURE,
  MISDIRECTED_REQUEST,
  UNPROCESSABLE_ENTITY,
  LOCKED,
  FAILED_DEPENDENCY,
  UPGRADE_REQUIRED,
  PRECONDITION_REQUIRED,
  TOO_MANY_REQUESTS,
  REQUEST_HEADER_FIELDS_TOO_LARGE,
  UNAVAILABLE_FOR_LEGAL_REASONS,
  INTERNAL_SERVER_ERROR,
  NOT_IMPLEMENTED,
  BAD_GATEWAY,
  SERVICE_UNAVAILABLE,
  GATEWAY_TIMEOUT,
  HTTP_VERSION_NOT_SUPPORTED,
  VARIANT_ALSO_NEGOTIATES,
  INSUFFICIENT_STORAGE,
  LOOP_DETECTED,
  NOT_EXTENDED,
  NETWORK_AUTHENTICATION_REQUIRED,
  NETWORK_CONNECT_TIMEOUT_ERROR,
};
```

```js title="src/constants/path.constant.js"

  LOGS_ALL,
  LOGS_ERROR,
};
```

and we'll add to other constants in the future.

after creating the directory structure, we'll create a file called `.env` and `.env.example` in the root directory:

- `.env`: This file will contain the configuration for the application.
- `.env.example`: is the file that contains all of the configurations for constants that `.env` has, but without values; only this one is versioned. `env.example` serves as a template for building a `.env` file that contains the information required to start the program.

```shell title="Create the .env files"
cd ..

touch .env .env.example
```

Now we add a new variable to `.env`:

```shell title=".env"
NODE_ENV=development
# NODE_ENV=production
PORT=3030
DATABASE_URL=mongodb://127.0.0.1:27017/example
```

```shell title=".env.example"
NODE_ENV=development
# NODE_ENV=production
PORT=3030
DATABASE_URL=mongodb://
```

after creating the directory structure and `.env` files, we'll create a file called `index.js`, `bin/www.js`, `config/db.config.js` and `env/variable.env.js` in the `src` directory.

```shell title="Create the index.js, bin/www.js, db.config.js and variable.env.js files"
touch src/index.js src/bin/www.js src/config/db.config.js src/env/variable.env.js
```

new files will be created in the `src` directory, and we'll add the following code to each of them:

```js title="src/config/db.config.js"

const connectDb = async (URL) => {
  const connectionParams = {
    useNewUrlParser: true,
    useUnifiedTopology: true,
  };

  try {
    const connection = await connect(URL, connectionParams);
    logger.info(`Mongo DB is connected to: ${connection.connection.host}`);
  } catch (err) {
    logger.error(`An error ocurred\n\r\n\r${err}`);
  }
};

```

```js title="src/env/variable.env.js"

dotenv.config();

  NODE_ENV,
  PORT,
  DATABASE_URL,
};
```

```js title="src/index.js"

//http constant

connectDb(DATABASE_URL);

const app = express();

app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(cors());

app.get('/', (req, res, next) => {
  try {
    res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      API: 'Work',
    });
  } catch (err) {
    next(err);
  }
});

```

```js title="src/bin/www.js"
#!/user/bin/env node

/**
 * Normalize a port into a number, string, or false.
 */
const normalizePort = (val) => {
  const port = parseInt(val, 10);

  if (Number.isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
};

const port = normalizePort(PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */
const server = http.createServer(app);

/**
 * Event listener for HTTP server "error" event.
 */
const onError = (error) => {
  if (error.syscall !== 'listen') {
    throw error;
  }

  const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      logger.error(`${bind} requires elevated privileges`);
      process.exit(1);
      break;
    case 'EADDRINUSE':
      logger.error(`${bind} is already in use`);
      process.exit(1);
      break;
    default:
      throw error;
  }
};

/**
 * Event listener for HTTP server "listening" event.
 */
const onListening = () => {
  const addr = server.address();
  const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
  logger.info(`Listening on ${bind}`);
};

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
```

Now we add a new script to `package.json` so we can run the application:

```json title="package.json" collapse={1-6, 18-66}
{
  "name": "tutorial",
  "version": "0.0.0",
  "description": "",
  "keywords": [],
  "main": "index.js",
  "scripts": {
    "start": "node build/bin/www.js",
    "dev": "tsx watch src/bin/www.js",
    "clean": "rm -rf build",
    "build": "pnpm clean && pnpm exec babel src -d build --minified --presets @babel/preset-env",
    "lint": "eslint \"src/**/*.js\" --fix",
    "lint:check": "eslint \"src/**/*.js\"",
    "prettier": "prettier --write \"src/**/*.js\"",
    "prettier:check": "prettier --check \"src/**/*.js\"",
    "prepare": "husky install"
  },
  "license": "MIT",
  "author": {
    "name": "YOUR_NAME",
    "email": "YOUR_EMAIL",
    "url": "YOUR_WEBSITE"
  },
  "homepage": "YOUR_GIT_REPO_URL#readme",
  "repository": {
    "type": "git",
    "url": "git+YOUR_GIT_REPO_URL.git"
  },
  "bugs": {
    "url": "YOUR_GIT_REPO_URL/issues"
  },
  "engines": {
    "node": ">=14.0.0",
    "pnpm": ">=8.0.0"
  },
  "dependencies": {
    "@babel/core": "7.18.5",
    "@babel/preset-env": "7.18.2",
    "compression": "1.7.4",
    "cookie-parser": "1.4.6",
    "core-js": "3.23.2",
    "cors": "2.8.5",
    "crypto-js": "4.1.1",
    "dotenv": "16.0.1",
    "express": "4.18.1",
    "helmet": "5.1.0",
    "husky": "8.0.1",
    "jsonwebtoken": "9.0.0",
    "mongoose": "6.11.3",
    "regenerator-runtime": "0.13.9",
    "winston": "3.8.0"
  },
  "devDependencies": {
    "@babel/cli": "7.17.10",
    "@commitlint/cli": "17.0.3",
    "@commitlint/config-conventional": "17.0.3",
    "babel-plugin-module-resolver": "4.1.0",
    "eslint": "8.18.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-prettier": "4.0.0",
    "prettier": "2.7.1",
    "tsx": "4.19.2"
  }
}
```

New you can run the application with `pnpm start` or `pnpm dev`, and you can also run the application with `pnpm build` to create a production version.

```shell title="Run the application"
pnpm dev

pnpm start

pnpm build
```

New we'll add a new package:

[compression](https://www.npmjs.com/package/compression): Your Node.js app's main file contains middleware for `compression`. GZIP, which supports a variety of `compression` techniques, will then be enabled. Your JSON response and any static file replies will be smaller as a result.

```shell title="Install compression"
pnpm add compression
```

[cookie-parser](https://www.npmjs.com/package/cookie-parser): Your Node.js app's main file contains middleware for `cookie-parser`. This middleware will parse the cookies in the request and set them as properties of the request object.

```shell title="Install cookie-parser"
pnpm add cookie-parser
```

[core-js](https://www.npmjs.com/package/core-js): Your Node.js app's main file contains middleware for `core-js`. This middleware will add the necessary polyfills to your application.

```shell title="Install core-js"
pnpm add core-js
```

[helmet](https://www.npmjs.com/package/helmet): Your Node.js app's main file contains middleware for `helmet`. This middleware will add security headers to your application.

```shell title="Install helmet"
pnpm add helmet
```

[regenerator-runtime](https://www.npmjs.com/package/regenerator-runtime): Your Node.js app's main file contains middleware for `regenerator-runtime`. This middleware will add the necessary polyfills to your application.

```shell title="Install regenerator-runtime"
pnpm add regenerator-runtime
```

we'll change the `index.js` file:

```js title="src/index.js"

//http constant

connectDb(DATABASE_URL);

const app = express();

//helmet
app.use(helmet());

app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(compression());
app.use(cors());
app.use(cookieParser());

app.get('/', (req, res, next) => {
  try {
    res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      API: 'Work',
    });
  } catch (err) {
    next(err);
  }
});

```

and we'll change the `bin/www.js` file:

```js title="src/bin/www.js"
#!/user/bin/env node

/**
 * Normalize a port into a number, string, or false.
 */
const normalizePort = (val) => {
  const port = parseInt(val, 10);

  if (Number.isNaN(port)) {
    // named pipe
    return val;
  }

  if (port >= 0) {
    // port number
    return port;
  }

  return false;
};

const port = normalizePort(PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */
const server = http.createServer(app);

/**
 * Event listener for HTTP server "error" event.
 */
const onError = (error) => {
  if (error.syscall !== 'listen') {
    throw error;
  }

  const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      logger.error(`${bind} requires elevated privileges`);
      process.exit(1);
      break;
    case 'EADDRINUSE':
      logger.error(`${bind} is already in use`);
      process.exit(1);
      break;
    default:
      throw error;
  }
};

/**
 * Event listener for HTTP server "listening" event.
 */
const onListening = () => {
  const addr = server.address();
  const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
  logger.info(`Listening on ${bind}`);
};

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
```

New we'll run the application with `pnpm dev`:

```shell title="Run the application"
╭─mkabumattar@mkabumattar in ~/work/tutorial is  v0.0.0 via  v18.3.0 took 243ms
╰─λ pnpm dev

> tutorial@0.0.0 dev
> tsx watch src/bin/www.js

2022-06-25 11:57:38:5738 info: Listening on port 3030
2022-06-25 11:57:38:5738 info: Mongo DB is connected to: 127.0.0.1
```

## Git Hooks

Before moving on to component development, there is one more section on configuration. If you want to expand on this project in the future, especially with a team of other developers, keep in mind that you'll want it to be as stable as possible. To get it right from the beginning is time well spent.

We're going to use a program called [Husky](https://husky.run/).

### Husky

Husky is a tool for executing scripts at various git stages, such as add, commit, push, etc. We would like to be able to specify requirements and, provided our project is of acceptable quality, only enable actions like commit and push to proceed if our code satisfies those requirements.

To install Husky run

```shell title="Install Husky"
pnpm add husky

git init

pnpm exec husky install
```

This will create a `.husky` directory in your project. Your hooks will be located here. As it is meant for other developers as well as yourself, make sure this directory is included in your code repository.

```shell title="Create the .gitignore file"
touch .gitignore
```

```yaml title=".gitignore"
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp
.cache

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
```

A `.husky` directory will be created in your project by the second command. Your hooks will be located here. As it is meant for other developers as well as yourself, make sure this directory is included in your code repository.

Add the following script to your `package.json` file:

```json title="package.json" collapse={1-6, 8-15, 18-66}
{
  "name": "tutorial",
  "version": "0.0.0",
  "description": "",
  "keywords": [],
  "main": "index.js",
  "scripts": {
    "start": "node build/bin/www.js",
    "dev": "tsx watch src/bin/www.js",
    "clean": "rm -rf build",
    "build": "pnpm clean && pnpm exec babel src -d build --minified --presets @babel/preset-env",
    "lint": "eslint \"src/**/*.js\" --fix",
    "lint:check": "eslint \"src/**/*.js\"",
    "prettier": "prettier --write \"src/**/*.js\"",
    "prettier:check": "prettier --check \"src/**/*.js\"",
    "prepare": "husky install"
  },
  "license": "MIT",
  "author": {
    "name": "YOUR_NAME",
    "email": "YOUR_EMAIL",
    "url": "YOUR_WEBSITE"
  },
  "homepage": "YOUR_GIT_REPO_URL#readme",
  "repository": {
    "type": "git",
    "url": "git+YOUR_GIT_REPO_URL.git"
  },
  "bugs": {
    "url": "YOUR_GIT_REPO_URL/issues"
  },
  "engines": {
    "node": ">=14.0.0",
    "pnpm": ">=8.0.0"
  },
  "dependencies": {
    "@babel/core": "7.18.5",
    "@babel/preset-env": "7.18.2",
    "compression": "1.7.4",
    "cookie-parser": "1.4.6",
    "core-js": "3.23.2",
    "cors": "2.8.5",
    "crypto-js": "4.1.1",
    "dotenv": "16.0.1",
    "express": "4.18.1",
    "helmet": "5.1.0",
    "husky": "8.0.1",
    "jsonwebtoken": "9.0.0",
    "mongoose": "6.11.3",
    "regenerator-runtime": "0.13.9",
    "winston": "3.8.0"
  },
  "devDependencies": {
    "@babel/cli": "7.17.10",
    "@commitlint/cli": "17.0.3",
    "@commitlint/config-conventional": "17.0.3",
    "babel-plugin-module-resolver": "4.1.0",
    "eslint": "8.18.0",
    "eslint-config-airbnb-base": "15.0.0",
    "eslint-config-prettier": "8.5.0",
    "eslint-plugin-import": "2.26.0",
    "eslint-plugin-prettier": "4.0.0",
    "prettier": "2.7.1",
    "tsx": "4.19.2"
  }
}
```

To create a hook run:

```shell title="Create a hook"
pnpm exec husky add .husky/pre-commit "pnpm lint"
```

The aforementioned states that the `pnpm lint` script must run and be successful before our commit may be successful. Success here refers to the absence of mistakes. You will be able to get warnings (remember in the ESLint config a setting of 1 is a warning and 2 is an error in case you want to adjust settings).

We're going to add another one:

```shell title="Create a hook"
pnpm exec husky add .husky/pre-push "pnpm build"
```

This makes sure that we can't push to the remote repository until our code has built correctly. That sounds like a very acceptable requirement, don't you think? By making this adjustment and attempting to push, feel free to test it.

### Commitlint

Finally, we'll add one more tool. Let's make sure that everyone on the team is adhering to them as well (including ourselves! ), since we have been using a uniform format for all of our commit messages so far. For our commit messages, we may add a linter.

```shell title="Install Commitlint"
pnpm add -D @commitlint/config-conventional @commitlint/cli
```

We will configure it using a set of common defaults, but since I occasionally forget what prefixes are available, I like to explicitly provide that list in a `commitlint.config.js` file:

```shell title="Create a commitlint.config.js file"
touch commitlint.config.js
```

```js title="commitlint.config.js"
// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
// docs: Documentation only changes
// feat: A new feature
// fix: A bug fix
// perf: A code change that improves performance
// refactor: A code change that neither fixes a bug nor adds a feature
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
// test: Adding missing tests or correcting existing tests

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'body-leading-blank': [1, 'always'],
    'body-max-line-length': [2, 'always', 100],
    'footer-leading-blank': [1, 'always'],
    'footer-max-line-length': [2, 'always', 100],
    'header-max-length': [2, 'always', 100],
    'scope-case': [2, 'always', 'lower-case'],
    'subject-case': [
      2,
      'never',
      ['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
    ],
    'subject-empty': [2, 'never'],
    'subject-full-stop': [2, 'never', '.'],
    'type-case': [2, 'always', 'lower-case'],
    'type-empty': [2, 'never'],
    'type-enum': [
      2,
      'always',
      [
        'build',
        'chore',
        'ci',
        'docs',
        'feat',
        'fix',
        'perf',
        'refactor',
        'revert',
        'style',
        'test',
        'translation',
        'security',
        'changeset',
      ],
    ],
  },
};
```

Afterward, use Husky to enable commitlint by using:

```shell title="Create a hook"
pnpm exec husky add .husky/commit-msg 'pnpm exec commitlint --edit "$1"'
```

Now connect this repository to GitHub and push your commits. This uses an SSH remote (`git@github.com:`), so set up your [SSH keys](/blog/post/git-ssh-keys-for-github-gitlab-and-bitbucket-on-linux) first if you haven't already.

```shell title="Push to GitHub"
echo "# Setting up Node JS, Express, MongoDB, Prettier, ESLint and Husky Application with Babel and authentication as an example" >> README.md
git init
git add README.md
git commit -m "ci: Initial commit"
git branch -M main
git remote add origin git@github.com:<your-github-username>/<your-github-repository-name>.git
git push -u origin main
```

## VS Code

### Configuration

We can now take advantage of some useful VS Code functionality to have ESLint and Prettier run automatically since we have implemented them.

Make a `settings.json` file and a directory called `.vscode` at the top of your project. This will be a list of values that overrides the VS Code installation's default settings.

Because we may set up particular parameters that only apply to this project and share them with the rest of our team by adding them to the code repository, we want to put them in a folder for the project.

Within `settings.json` we will add the following values:

```shell title="Create the settings.json file"
mkdir .vscode
touch .vscode/settings.json
```

`settings.json`

```json title="./
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll": true,
    "source.organizeImports": true
  }
}
```

### Debugging

In case we encounter any problems while developing our program, let's set up a handy environment for debugging.

Inside of your `.vscode` directory create a `launch.json` file:

```shell
touch .vscode/launch.json
```

```json title=".vscode/launch.json"
{
  "version": "0.1.0",
  "configurations": [
    {
      "name": "debug server",
      "type": "node-terminal",
      "request": "launch",
      "command": "pnpm dev"
    }
  ]
}
```

## Authentication

### Authentication Setup

We'll add a new packages to our project:

- [crypto-js](https://www.npmjs.com/package/crypto-js): A JavaScript library for encryption and decryption.
- [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken): A JavaScript library for creating and verifying JSON Web Tokens.

```shell title="Install crypto-js and jsonwebtoken"
pnpm add crypto-js jsonwebtoken
```

add secret key to `.env` file:

```shell title=".env"
...
JWT_SECRET=secret
PASS_SECRET=secret
```

add `JWT_SECRET` and `PASS_SECRET` to `variable.env.js` file:

```js title="src/env/variable.env.js"
...

  ...,
  JWT_SECRET,
  PASS_SECRET,
}
```

know we'll add the constants to:

```js title="src/constants/api.constant.js"
// api

// auth

// users

  // api
  API_AUTH,
  API_USERS,

  // auth
  AUTH_REGISTER,
  AUTH_LOGIN,

  // users
  USER_UPDATE_USERNAME,
  USER_UPDATE_NAME,
  USER_UPDATE_EMAIL,
  USER_UPDATE_PASSWORD,
  USER_UPDATE_PHONE,
  USER_UPDATE_ADDRESS,
  USER_DELETE,
  USER_GET,
  USER_GET_ALL,
  USER_GET_ALL_STATS,
};
```

```js title="src/constants/message.constant.js"
// token

// auth

// user

  // token
  TOKEN_NOT_VALID,
  NOT_AUTHENTICATED,
  NOT_ALLOWED,

  // auth
  USERNAME_NOT_VALID,
  NAME_NOT_VALID,
  EMAIL_NOT_VALID,
  PASSWORD_NOT_VALID,
  PHONE_NOT_VALID,
  ADDRESS_NOT_VALID,
  USERNAME_EXIST,
  EMAIL_EXIST,
  PHONE_EXIST,
  USER_NOT_CREATE,
  USER_CREATE_SUCCESS,
  USER_NOT_FOUND,
  PASSWORD_NOT_MATCH,
  USER_LOGIN_SUCCESS,

  // user
  USERNAME_NOT_CHANGE,
  USERNAME_CHANGE_SUCCESS,
  NAME_NOT_CHANGE,
  NAME_CHANGE_SUCCESS,
  EMAIL_NOT_CHANGE,
  EMAIL_CHANGE_SUCCESS,
  PASSWORD_NOT_CHANGE,
  PASSWORD_CHANGE_SUCCESS,
  PHONE_NOT_CHANGE,
  PHONE_CHANGE_SUCCESS,
  ADDRESS_NOT_CHANGE,
  ADDRESS_CHANGE_SUCCESS,
  USER_NOT_DELETE,
  USER_DELETE_SUCCESS,
  USER_FOUND,
};
```

```js title="src/constants/model.constant.js"

  USER_MODEL,
};
```

```js title="src/constants/number.constant.js"
// user

  USERNAME_MIN_LENGTH,
  USERNAME_MAX_LENGTH,
  NAME_MIN_LENGTH,
  NAME_MAX_LENGTH,
  EMAIL_MAX_LENGTH,
  PASSWORD_MIN_LENGTH,
  PHONE_MIN_LENGTH,
  PHONE_MAX_LENGTH,
  ADDRESS_MIN_LENGTH,
  ADDRESS_MAX_LENGTH,
};
```

```js title="src/constants/regex.constant.js"

  /^(([^<>()\\[\]\\.,;:\s@"]+(\.[^<>()\\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;

  /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;

  /^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$/gm;

  USERNAME,
  EMAIL,
  PASSWORD,
  NAME,
  PHONE,
  ADDRESS,
};
```

### Authentication Middleware

```js title="src/middlewares/token.middleware.js"
// http constant

// logger

  const authHeader = req.headers.token;
  logger.info(`authHeader: ${authHeader}`);
  if (authHeader) {
    const token = authHeader.split(' ')[1];
    return jwt.verify(token, JWT_SECRET, (err, user) => {
      if (err) {
        res.status(ConstantHttpCode.FORBIDDEN).json({
          status: {
            code: ConstantHttpCode.FORBIDDEN,
            msg: ConstantHttpReason.FORBIDDEN,
          },
          msg: ConstantMessage.TOKEN_NOT_VALID,
        });
      }
      req.user = user;
      return next();
    });
  }

  return res.status(ConstantHttpCode.UNAUTHORIZED).json({
    status: {
      code: ConstantHttpCode.UNAUTHORIZED,
      msg: ConstantHttpReason.UNAUTHORIZED,
    },
    msg: ConstantMessage.NOT_AUTHENTICATED,
  });
};

  verifyToken(req, res, () => {
    if (req.user.id === req.params.id || req.user.isAdmin) {
      return next();
    }

    return res.status(ConstantHttpCode.FORBIDDEN).json({
      status: {
        code: ConstantHttpCode.FORBIDDEN,
        msg: ConstantHttpReason.FORBIDDEN,
      },
      msg: ConstantMessage.NOT_ALLOWED,
    });
  });
};

  verifyToken(req, res, () => {
    if (req.user.isAdmin) {
      return next();
    }

    return res.status(ConstantHttpCode.FORBIDDEN).json({
      status: {
        code: ConstantHttpCode.FORBIDDEN,
        msg: ConstantHttpReason.FORBIDDEN,
      },
      msg: ConstantMessage.NOT_ALLOWED,
    });
  });
};

  verifyToken,
  verifyTokenAndAuthorization,
  verifyTokenAndAdmin,
};
```

### Authentication Security

```js title="src/security/user.security.js"

  return CryptoJS.AES.encrypt(password, PASS_SECRET).toString();
};

  return CryptoJS.AES.decrypt(password, PASS_SECRET).toString(
    CryptoJS.enc.Utf8,
  );
};

  const compare = decryptedPassword(dPassword) === password;
  return compare;
};

  return jwt.sign({id, isAdmin}, JWT_SECRET, {expiresIn: '3d'});
};

  encryptedPassword,
  decryptedPassword,
  comparePassword,
  generateAccessToken,
};
```

### Authentication validations

```js title="src/validations/user.validation.js"

  return ConstantRegex.USERNAME.test(username);
};

  return ConstantRegex.NAME.test(name);
};

  return ConstantRegex.EMAIL.test(email);
};

  return ConstantRegex.PASSWORD.test(password);
};

  return ConstantRegex.PHONE.test(phone);
};

  return ConstantRegex.ADDRESS.test(address);
};

  validateUsername,
  validateName,
  validateEmail,
  validatePassword,
  validatePhone,
  validateAddress,
};
```

### Authentication schemas

```js title="src/schemas/user.schema.js"

const UserSchema = new mongoose.Schema(
  {
    username: {
      type: String,
      required: true,
      unique: true,
      min: ConstantNumber.USERNAME_MIN_LENGTH,
      max: ConstantNumber.USERNAME_MAX_LENGTH,
    },
    name: {
      type: String,
      required: true,
      min: ConstantNumber.NAME_MIN_LENGTH,
      max: ConstantNumber.NAME_MAX_LENGTH,
    },
    email: {
      type: String,
      required: true,
      unique: true,
      max: ConstantNumber.EMAIL_MAX_LENGTH,
    },
    password: {
      type: String,
      required: true,
      min: ConstantNumber.PASSWORD_MIN_LENGTH,
    },
    phone: {
      type: String,
      required: true,
      unique: true,
      min: ConstantNumber.PHONE_MIN_LENGTH,
      max: ConstantNumber.PHONE_MAX_LENGTH,
    },
    address: {
      type: String,
      required: true,
      min: ConstantNumber.ADDRESS_MIN_LENGTH,
      max: ConstantNumber.ADDRESS_MAX_LENGTH,
    },
    isAdmin: {
      type: Boolean,
      default: true,
    },
  },
  {
    versionKey: false,
    timestamps: true,
  },
);

```

### Authentication Models

```js title="src/models/user.model.js"

const UserModel = mongoose.model(ConstantModel.USER_MODEL, UserSchema);

```

### Authentication Repositories

```js title="src/repositories/user.repository.js"

  const users = await User.find({}).select('-password');
  return users;
};

  const user = await User.findById(id).select('-password');
  return user;
};

  const user = await User.findById(id);
  return user;
};

  const user = await User.findOne({username}).select('-password');
  return user;
};

  const user = await User.findOne({email}).select('-password');
  return user;
};

  const user = await User.findOne({email});
  return user;
};

  const user = await User.findOne({phone}).select('-password');
  return user;
};

  const newUser = new User({
    username: user.username,
    name: user.name,
    email: user.email,
    password: user.password,
    phone: user.phone,
    address: user.address,
    isAdmin: user.isAdmin,
  });
  const savedUser = await newUser.save();
  return savedUser;
};

  const user = await User.findByIdAndUpdate(id, {username}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndUpdate(id, {name}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndUpdate(id, {email}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndUpdate(id, {password}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndUpdate(id, {phone}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndUpdate(id, {address}, {new: true}).select(
    '-password',
  );
  return user;
};

  const user = await User.findByIdAndDelete(id);
  return user;
};

  const users = await User.aggregate([
    {$match: {createdAt: {$gte: lastYear}}},
    {
      $project: {
        month: {$month: '$createdAt'},
      },
    },
    {
      $group: {
        _id: '$month',
        total: {$sum: 1},
      },
    },
  ]);
  return users;
};

  findAll,
  findById,
  findByIdWithPassword,
  findByUser,
  findByEmail,
  findByEmailWithPassword,
  findByPhone,
  createUser,
  updateUsername,
  updateName,
  updateEmail,
  updatePassword,
  updatePhone,
  updateAddress,
  deleteUser,
  getUsersStats,
};
```

### Authentication Services

```js title="src/services/auth.service.js"

  return UserValidation.validateUsername(username);
};

  return UserValidation.validateName(name);
};

  return UserValidation.validateEmail(email);
};

  return UserValidation.validatePassword(password);
};

  return UserSecurity.comparePassword(password, encryptedPassword);
};

  return UserValidation.validatePhone(phone);
};

  return UserValidation.validateAddress(address);
};

  const user = await UserRepository.findByUser(username);
  return user;
};

  const user = await UserRepository.findByEmailWithPassword(email);
  return user;
};

  const user = await UserRepository.findByPhone(phone);
  return user;
};

  const encryptedPassword = UserSecurity.encryptedPassword(user.password);
  const newUser = {
    username: user.username,
    name: user.name,
    email: user.email,
    password: encryptedPassword,
    phone: user.phone,
    address: user.address,
    isAdmin: user.isAdmin,
  };
  const savedUser = await UserRepository.createUser(newUser);
  return savedUser;
};

  return `Bearer ${UserSecurity.generateAccessToken(user.id, user.isAdmin)}`;
};

  validateUsername,
  validateName,
  validateEmail,
  validatePassword,
  comparePassword,
  validatePhone,
  validateAddress,
  findByUser,
  findByEmail,
  findByPhone,
  createUser,
  generateAccessToken,
};
```

```js title="src/services/user.service.js"

  return UserValidation.validateUsername(username);
};

  return UserValidation.validateName(name);
};

  return UserValidation.validateEmail(email);
};

  return UserValidation.validatePassword(name);
};

  return UserSecurity.comparePassword(password, encryptedPassword);
};

  return UserValidation.validatePhone(phone);
};

  return UserValidation.validateAddress(address);
};

  const users = await UserRepository.findAll();
  return users;
};

  const user = await UserRepository.findByIdWithPassword(id);
  return user;
};

  const user = await UserRepository.findById(id);
  return user;
};

  const user = await UserRepository.findByEmail(email);
  return user;
};

  const user = await UserRepository.findByPhone(phone);
  return user;
};

  const user = await UserRepository.findByUser(username);
  return user;
};

  const user = await UserRepository.updateUsername(id, username);
  return user;
};

  const user = await UserRepository.updateName(id, name);
  return user;
};

  const user = await UserRepository.updateEmail(id, email);
  return user;
};

  const encryptedPassword = UserSecurity.encryptedPassword(password);
  const user = await UserRepository.updatePassword(id, encryptedPassword);
  return user;
};

  const user = await UserRepository.updatePhone(id, phone);
  return user;
};

  const user = await UserRepository.updateAddress(id, address);
  return user;
};

  const user = await UserRepository.deleteUser(id);
  return user;
};

  const users = await UserRepository.getUsersStats();
  return users;
};

  validateUsername,
  validateName,
  validateEmail,
  validatePassword,
  comparePassword,
  validatePhone,
  validateAddress,
  findAll,
  findById,
  findByIdWithOutPassword,
  findByEmail,
  findByPhone,
  findByUser,
  updateUsername,
  updateName,
  updateEmail,
  updatePassword,
  updatePhone,
  updateAddress,
  deleteUser,
  getUsersStats,
};
```

### Authentication Controllers

`auth.controller.js`

```js
// http constant

// logger

  try {
    const {username, name, email, password, phone, address} = req.body;

    const usernameValidated = AuthServices.validateUsername(username);
    if (!usernameValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_NOT_VALID,
      });
    }
    logger.info(`username ${username} is valid`);

    const nameValidated = AuthServices.validateName(name);
    if (!nameValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.NAME_NOT_VALID,
      });
    }
    logger.info(`name ${name} is valid`);

    const emailValidated = AuthServices.validateEmail(email);
    if (!emailValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_NOT_VALID,
      });
    }
    logger.info(`email ${email} is valid`);

    const passwordValidated = AuthServices.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }

    const phoneValidated = AuthServices.validatePhone(phone);
    if (!phoneValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_NOT_VALID,
      });
    }

    const addressValidated = AuthServices.validateAddress(address);
    if (!addressValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.ADDRESS_NOT_VALID,
      });
    }

    const usernameCheck = await AuthServices.findByUser(username);
    if (usernameCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_EXIST,
      });
    }

    const emailCheck = await AuthServices.findByEmail(email);
    if (emailCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_EXIST,
      });
    }

    const phoneCheck = await AuthServices.findByPhone(phone);
    if (phoneCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_EXIST,
      });
    }

    const newUserData = {
      username,
      name,
      email,
      password,
      phone,
      address,
    };

    const user = await AuthServices.createUser(newUserData);
    if (!user) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USER_NOT_CREATE,
      });
    }

    const newUser = {...user}._doc;

    logger.info({newUserpassword: newUser.password});

    delete newUser.password;

    logger.info({newUserpassword: newUser.password});

    return res.status(ConstantHttpCode.CREATED).json({
      status: {
        code: ConstantHttpCode.CREATED,
        msg: ConstantHttpReason.CREATED,
      },
      msg: ConstantMessage.USER_CREATE_SUCCESS,
      data: user,
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {email, password} = req.body;

    const emailValidated = AuthServices.validateEmail(email);
    if (!emailValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_NOT_VALID,
      });
    }

    const passwordValidated = AuthServices.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }

    const user = await AuthServices.findByEmail(email);
    if (!user) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }

    const isMatch = AuthServices.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }

    const accessToken = await AuthServices.generateAccessToken(user);
    logger.info(`accessToken: ${accessToken}`);

    const newUser = {...user}._doc;

    logger.info({newUserpassword: newUser.password});

    delete newUser.password;

    logger.info({newUserpassword: newUser.password});

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.LOGIN_SUCCESS,
      data: {
        user,
        accessToken,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  register,
  login,
};
```

`user.controller.js`

```js
// http constant

// logger

  try {
    const {username, password} = req.body;
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const usernameValidated = UserService.validateUsername(username);
    if (!usernameValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_NOT_VALID,
      });
    }
    logger.info(`username ${username} is valid`);

    const passwordValidated = UserService.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${password} is valid`);

    const isMatch = UserService.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }

    const usernameCheck = await UserService.findByUser(username);
    if (usernameCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_EXIST,
      });
    }

    if (user.username === username) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_NOT_CHANGE,
      });
    }

    const updatedUser = await UserService.updateUsername(id, username);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USERNAME_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.USERNAME_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {name, password} = req.body;
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const nameValidated = UserService.validateName(name);
    if (!nameValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.NAME_NOT_VALID,
      });
    }

    const passwordValidated = UserService.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }

    const isMatch = UserService.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }
    logger.info(`password ${password} is valid`);

    if (user.name === name) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.NAME_NOT_CHANGE,
      });
    }
    logger.info(`name ${name} is valid`);

    const updatedUser = await UserService.updateName(id, name);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.NAME_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.NAME_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {email, password} = req.body;
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const emailValidated = UserService.validateEmail(email);
    if (!emailValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_NOT_VALID,
      });
    }
    logger.info(`email ${email} is valid`);

    const passwordValidated = UserService.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${password} is valid`);

    const isMatch = UserService.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }
    logger.info(`password ${password} is valid`);

    if (user.email === email) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_NOT_CHANGE,
      });
    }
    logger.info(`email ${email} is valid`);

    const emailCheck = await UserService.findByEmail(email);
    if (emailCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_EXIST,
      });
    }

    const updatedUser = await UserService.updateEmail(id, email);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.EMAIL_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.EMAIL_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {oldPassword, newPassword, confirmPassword} = req.body;
    const {id} = req.params;

    if (newPassword !== confirmPassword) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const oldPasswordValidated = UserService.validatePassword(oldPassword);
    if (!oldPasswordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${oldPassword} is valid`);

    const newPasswordValidated = UserService.validatePassword(newPassword);
    if (!newPasswordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${newPassword} is valid`);

    const confirmPasswordValidated =
      UserService.validatePassword(confirmPassword);
    if (!confirmPasswordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${confirmPassword} is valid`);

    if (oldPassword === newPassword) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_CHANGE,
      });
    }

    const isMatch = UserService.comparePassword(oldPassword, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }

    const updatedUser = await UserService.updatePassword(id, newPassword);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.PASSWORD_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {phone, password} = req.body;
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const phoneValidated = UserService.validatePhone(phone);
    if (!phoneValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_NOT_VALID,
      });
    }

    const passwordValidated = UserService.validatePassword(password);
    if (!passwordValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_VALID,
      });
    }
    logger.info(`password ${password} is valid`);

    const isMatch = UserService.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }
    logger.info(`password ${password} is valid`);

    if (user.phone === phone) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_NOT_CHANGE,
      });
    }

    const phoneCheck = await UserService.findByPhone(phone);
    if (phoneCheck) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_EXIST,
      });
    }

    const updatedUser = await UserService.updatePhone(id, phone);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PHONE_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.PHONE_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {address, password} = req.body;
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const addressValidated = UserService.validateAddress(address);
    if (!addressValidated) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.ADDRESS_NOT_VALID,
      });
    }

    const isMatch = UserService.comparePassword(password, user.password);
    if (!isMatch) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.PASSWORD_NOT_MATCH,
      });
    }
    logger.info(`password ${password} is valid`);

    if (user.address === address) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.ADDRESS_NOT_CHANGE,
      });
    }

    const updatedUser = await UserService.updateAddress(id, address);
    if (!updatedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.ADDRESS_NOT_CHANGE,
      });
    }
    logger.info(`user ${user.username} updated`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.ADDRESS_CHANGE_SUCCESS,
      data: {
        user: updatedUser,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {id} = req.params;

    const user = await UserService.findById(id);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    const deletedUser = await UserService.deleteUser(id);
    if (!deletedUser) {
      return res.status(ConstantHttpCode.BAD_REQUEST).json({
        status: {
          code: ConstantHttpCode.BAD_REQUEST,
          msg: ConstantHttpReason.BAD_REQUEST,
        },
        msg: ConstantMessage.USER_NOT_DELETE,
      });
    }
    logger.info(`user ${user.username} deleted`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.USER_DELETE_SUCCESS,
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const {id} = req.params;
    logger.info(`user ${id} found`);

    const user = await UserService.findByIdWithOutPassword(id);
    logger.info(`user ${user} found`);
    if (!user) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`user ${user.username} found`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.USER_FOUND,
      data: {
        user,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const users = await UserService.findAll();
    if (!users) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`users found`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.USER_FOUND,
      data: {
        users,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  try {
    const usersStats = await UserService.getUsersStats();
    if (!usersStats) {
      return res.status(ConstantHttpCode.NOT_FOUND).json({
        status: {
          code: ConstantHttpCode.NOT_FOUND,
          msg: ConstantHttpReason.NOT_FOUND,
        },
        msg: ConstantMessage.USER_NOT_FOUND,
      });
    }
    logger.info(`users stats found`);

    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      msg: ConstantMessage.USER_FOUND,
      data: {
        users: usersStats,
      },
    });
  } catch (err) {
    return next(err);
  }
};

  updateUsername,
  updateName,
  updateEmail,
  updatePassword,
  updatePhone,
  updateAddress,
  deleteUser,
  getUser,
  getUsers,
  getUsersStats,
};
```

### Authentication Routes

`auth.router.js`

```js

const router = express.Router();

router.post(ConstantAPI.AUTH_REGISTER, AuthController.register);
router.post(ConstantAPI.AUTH_LOGIN, AuthController.login);

```

`user.router.js`

```js

const router = express.Router();

router.post(
  ConstantAPI.USER_UPDATE_USERNAME,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updateUsername,
);
router.post(
  ConstantAPI.USER_UPDATE_NAME,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updateName,
);
router.post(
  ConstantAPI.USER_UPDATE_EMAIL,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updateEmail,
);
router.post(
  ConstantAPI.USER_UPDATE_PASSWORD,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updatePassword,
);
router.post(
  ConstantAPI.USER_UPDATE_PHONE,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updatePhone,
);
router.post(
  ConstantAPI.USER_UPDATE_ADDRESS,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.updateAddress,
);
router.post(
  ConstantAPI.USER_DELETE,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.deleteUser,
);
router.get(
  ConstantAPI.USER_GET,
  TokenMiddleware.verifyTokenAndAuthorization,
  UserController.getUser,
);
router.get(
  ConstantAPI.USER_GET_ALL,
  TokenMiddleware.verifyTokenAndAdmin,
  UserController.getUsers,
);
router.get(
  ConstantAPI.USER_GET_ALL_STATS,
  TokenMiddleware.verifyTokenAndAdmin,
  UserController.getUsersStats,
);

```

edit `index.js`

```js

// api constant

// http constant

// routers

connectDb(DATABASE_URL);

const app = express();

// helmet
app.use(helmet());

app.use(express.urlencoded({extended: true}));
app.use(express.json());
app.use(compression());
app.use(cors());
app.use(cookieParser());

app.get('/', (req, res, next) => {
  try {
    return res.status(ConstantHttpCode.OK).json({
      status: {
        code: ConstantHttpCode.OK,
        msg: ConstantHttpReason.OK,
      },
      API: 'Work',
    });
  } catch (err) {
    return next(err);
  }
});

app.use(ConstantAPI.API_AUTH, AuthRouter);
app.use(ConstantAPI.API_USERS, UserRouter);

```

## Frequently Asked Questions

> **Why use Babel for a Node.js backend at all?**

Babel lets you write modern ES module syntax (`import`/`export`) and the latest ECMAScript features, then transpile down to JavaScript your target Node version understands. You get one consistent, future-proof syntax across the codebase and a single `pnpm build` step that emits a deployable `build/` directory.

> **Can I use npm or yarn instead of pnpm?**

Yes. Every `pnpm add` maps to `npm install` / `yarn add`, and the `package.json` scripts run the same way with `npm run <script>` or `yarn <script>`. The `engine-strict` setting in `.npmrc` plus the `engines.pnpm` field just nudge contributors toward the package manager you standardize on.

> **What do the Husky hooks actually block?**

The `pre-commit` hook runs `pnpm lint`, so a commit fails if ESLint reports errors. The `pre-push` hook runs `pnpm build`, so you can't push code that doesn't compile. The `commit-msg` hook runs Commitlint, so commit messages must follow the conventional-commits format.

> **Why Winston instead of console.log?**

Winston gives you log levels, multiple transports (console plus rotating files), and structured timestamps so development output stays readable while errors persist to `logs/error.log` in production. `console.log` can't do level filtering or file output without extra work.

> **Is keeping the JWT secret in .env safe?**

`.env` is git-ignored, so the secret never lands in version control that's the whole point of the `.env` / `.env.example` split. In production, inject `JWT_SECRET` and `PASS_SECRET` through your host's environment or a secrets manager rather than a committed file.

## Summary

Finally, after compilation, we can now need to deploy the compiled version in the NodeJS production server.

All code from this tutorial as a complete package is available in this [repository](https://github.com/MKAbuMattar/template-express).

## References

- [Node.js Official Website](https://nodejs.org/)
- [Express.js Official Website](https://expressjs.com/)
- [MongoDB Official Website](https://www.mongodb.com/)
- [Mongoose ODM Official Website](https://mongoosejs.com/)
- [Babel Official Website](https://babeljs.io/)
- [ESLint Official Website](https://eslint.org/)
- [Prettier Official Website](https://prettier.io/)
- [Husky Official Website](https://typicode.github.io/husky/)
- [Commitlint Official Documentation](https://commitlint.js.org/)
- [JSON Web Tokens (JWT) Official Site](https://jwt.io/)
- [Winston Logger (GitHub)](https://github.com/winstonjs/winston)
- [Dotenv (npm package)](https://www.npmjs.com/package/dotenv)
- [tsx (npm package)](https://www.npmjs.com/package/tsx)
- [Helmet (npm package)](https://www.npmjs.com/package/helmet)
- [CryptoJS (Google Code Archive - for historical reference, often found on npm)](https://www.npmjs.com/package/crypto-js)
- [Git SSH Keys for GitHub, GitLab, and Bitbucket on Linux](/blog/post/git-ssh-keys-for-github-gitlab-and-bitbucket-on-linux)
- [Dotfiles: A Git-Based Strategy for Configuration Management](/blog/post/dotfiles)
