Type something to search...
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. 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.

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 yarn for this example, but you could just as easily use NPM if you choose, but yarn is a lot more convenient.

Create the project directory
mkdir backend-template
cd backend-template
yarn init -y

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.
Create the .nvmrc files
touch .nvmrc
echo "lts/fermium" > .nvmrc
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

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

open packages.json add the engines:

packages.json
1
{
2
"name": "tutorial",
3
"version": "0.0.0",
4
"description": "",
5
"keywords": [],
6
"main": "index.js",
7
"license": "MIT",
8
"author": {
9
"name": "Mohammad Abu Mattar",
10
"email": "mohammad.abumattar@outlook.com",
11
"url": "https://mkabumattar.github.io/"
12
},
13
"homepage": "YOUR_GIT_REPO_URL#readme",
14
"repository": {
15
"type": "git",
16
"url": "git+YOUR_GIT_REPO_URL.git"
17
},
18
"bugs": {
19
"url": "YOUR_GIT_REPO_URL/issues"
20
},
21
"engines": {
22
"node": ">=14.0.0",
23
"yarn": ">=1.20.0",
24
"npm": "please-use-yarn"
25
}
26
}

Babel Setup

In order to set up babel in the project, we must first install three main packages.

  • babel-core: The primary package for running any babel setup or configuration is babel-core.
  • babel-node: Any version of ES may be converted to ordinary JavaScript using the babel-node library.
  • babel-preset-env: This package gives us access to forthcoming functionalities that node.js does not yet comprehend. New features are constantly being developed, thus it will probably take some time for NodeJS to incorporate them.

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 NodeJS will comprehend the specific code or not.

Therefore, transiling the code before deployment is usually advised. Two different types of babel transpiling code exist.

Development Setup:

Install the babel packages
yarn global add nodemon
# for linux users
sudo yarn global add nodemon
Install the babel packages
yarn add express mongoose cors dotenv @babel/core @babel/node @babel/preset-env
## compression cookie-parser core-js crypto-js helmet jsonwebtoken lodash regenerator-runtime
Install the babel packages
yarn 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, babel-node, babel-preset-env, 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.

Create the .babelrc file
touch .babelrc
.babelrc
1
{
2
"presets": ["@babel/preset-env"]
3
}

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: A tool that will help us to format our code consistently.
  • ESLint: 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

Install Prettier
yarn add -D prettier

Additionally, I advise getting the Prettier VS Code extension so that you may avoid using the command line tool and have VS Code take care of the file formatting for you. It’s still required to include it here even when it’s installed and set up in your project since VSCode will utilize your project’s settings.

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.
.prettierrc
1
{
2
"trailingComma": "all",
3
"printWidth": 80,
4
"tabWidth": 2,
5
"useTabs": false,
6
"semi": false,
7
"singleQuote": true
8
}
.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:

package.json
6 collapsed lines
1
{
2
"name": "tutorial",
3
"version": "0.0.0",
4
"description": "",
5
"keywords": [],
6
"main": "index.js",
7
"scripts": {
6 collapsed lines
8
"start": "node build/bin/www.js",
9
"dev": "nodemon --exec babel-node src/bin/www.js",
10
"clean": "rm -rf build",
11
"build": "yarn clean && npx babel src -d build --minified --presets @babel/preset-env",
12
"lint": "eslint \"src/**/*.js\" --fix",
13
"lint:check": "eslint \"src/**/*.js\"",
14
"prettier": "prettier --write \"src/**/*.js\"",
15
"prettier:check": "prettier --check \"src/**/*.js\"",
16
"prepare": "husky install"
17
},
49 collapsed lines
18
"license": "MIT",
19
"author": {
20
"name": "YOUR_NAME",
21
"email": "YOUR_EMAIL",
22
"url": "YOUR_WEBSITE"
23
},
24
"homepage": "YOUR_GIT_REPO_URL#readme",
25
"repository": {
26
"type": "git",
27
"url": "git+YOUR_GIT_REPO_URL.git"
28
},
29
"bugs": {
30
"url": "YOUR_GIT_REPO_URL/issues"
31
},
32
"engines": {
33
"node": ">=14.0.0",
34
"yarn": ">=1.20.0",
35
"npm": "please-use-yarn"
36
},
37
"dependencies": {
38
"@babel/core": "7.18.5",
39
"@babel/node": "7.18.5",
40
"@babel/preset-env": "7.18.2",
41
"compression": "1.7.4",
42
"cookie-parser": "1.4.6",
43
"core-js": "3.23.2",
44
"cors": "2.8.5",
45
"crypto-js": "4.1.1",
46
"dotenv": "16.0.1",
47
"express": "4.18.1",
48
"helmet": "5.1.0",
49
"husky": "8.0.1",
50
"jsonwebtoken": "9.0.0",
51
"mongoose": "6.11.3",
52
"regenerator-runtime": "0.13.9",
53
"winston": "3.8.0"
54
},
55
"devDependencies": {
56
"@babel/cli": "7.17.10",
57
"@commitlint/cli": "17.0.3",
58
"@commitlint/config-conventional": "17.0.3",
59
"babel-plugin-module-resolver": "4.1.0",
60
"eslint": "8.18.0",
61
"eslint-config-airbnb-base": "15.0.0",
62
"eslint-config-prettier": "8.5.0",
63
"eslint-plugin-import": "2.26.0",
64
"eslint-plugin-prettier": "4.0.0",
65
"prettier": "2.7.1"
66
}
67
}

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

Run Prettier
yarn prettier:check
yarn 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.

Install ESLint
yarn 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.
.eslintrc
1
{
2
"extends": [
3
"airbnb-base",
4
"plugin:prettier/recommended",
5
"plugin:import/errors",
6
"plugin:import/warnings"
7
],
8
"plugins": ["prettier"],
9
"rules": {
10
"prettier/prettier": "error",
11
"import/no-named-as-default": "off",
12
"no-underscore-dangle": "off"
13
}
14
}
.eslintignore
node_modules
build

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

package.json
6 collapsed lines
1
{
2
"name": "tutorial",
3
"version": "0.0.0",
4
"description": "",
5
"keywords": [],
6
"main": "index.js",
7
"scripts": {
4 collapsed lines
8
"start": "node build/bin/www.js",
9
"dev": "nodemon --exec babel-node src/bin/www.js",
10
"clean": "rm -rf build",
11
"build": "yarn clean && npx babel src -d build --minified --presets @babel/preset-env",
12
"lint": "eslint \"src/**/*.js\" --fix",
13
"lint:check": "eslint \"src/**/*.js\"",
3 collapsed lines
14
"prettier": "prettier --write \"src/**/*.js\"",
15
"prettier:check": "prettier --check \"src/**/*.js\"",
16
"prepare": "husky install"
17
},
49 collapsed lines
18
"license": "MIT",
19
"author": {
20
"name": "YOUR_NAME",
21
"email": "YOUR_EMAIL",
22
"url": "YOUR_WEBSITE"
23
},
24
"homepage": "YOUR_GIT_REPO_URL#readme",
25
"repository": {
26
"type": "git",
27
"url": "git+YOUR_GIT_REPO_URL.git"
28
},
29
"bugs": {
30
"url": "YOUR_GIT_REPO_URL/issues"
31
},
32
"engines": {
33
"node": ">=14.0.0",
34
"yarn": ">=1.20.0",
35
"npm": "please-use-yarn"
36
},
37
"dependencies": {
38
"@babel/core": "7.18.5",
39
"@babel/node": "7.18.5",
40
"@babel/preset-env": "7.18.2",
41
"compression": "1.7.4",
42
"cookie-parser": "1.4.6",
43
"core-js": "3.23.2",
44
"cors": "2.8.5",
45
"crypto-js": "4.1.1",
46
"dotenv": "16.0.1",
47
"express": "4.18.1",
48
"helmet": "5.1.0",
49
"husky": "8.0.1",
50
"jsonwebtoken": "9.0.0",
51
"mongoose": "6.11.3",
52
"regenerator-runtime": "0.13.9",
53
"winston": "3.8.0"
54
},
55
"devDependencies": {
56
"@babel/cli": "7.17.10",
57
"@commitlint/cli": "17.0.3",
58
"@commitlint/config-conventional": "17.0.3",
59
"babel-plugin-module-resolver": "4.1.0",
60
"eslint": "8.18.0",
61
"eslint-config-airbnb-base": "15.0.0",
62
"eslint-config-prettier": "8.5.0",
63
"eslint-plugin-import": "2.26.0",
64
"eslint-plugin-prettier": "4.0.0",
65
"prettier": "2.7.1"
66
}
67
}

You can test out your config by running:

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

Run ESLint
yarn lint:check
yarn 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: 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: 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:

Install Winston
yarn add winston

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

Create the logger.util.js file
touch src/utils/logger.util.js
src/utils/logger.util.js
1
import winston from 'winston';
2
import {NODE_ENV} from '../env/variable.env';
3
4
const levels = {
5
error: 0,
6
warn: 1,
7
info: 2,
8
http: 3,
9
debug: 4,
10
};
11
12
const level = () => {
13
const env = NODE_ENV || 'development';
14
const isDevelopment = env === 'development';
15
return isDevelopment ? 'debug' : 'warn';
16
};
17
18
const colors = {
19
error: 'red',
20
warn: 'yellow',
21
info: 'green',
22
http: 'magenta',
23
debug: 'white',
24
};
25
26
winston.addColors(colors);
27
28
const format = winston.format.combine(
29
winston.format.timestamp({
30
format: 'YYYY-MM-DD HH:mm:ss:ms',
31
}),
32
winston.format.colorize({all: true}),
33
winston.format.printf(
34
(info) => `${info.timestamp} ${info.level}: ${info.message}`,
35
),
36
);
37
38
const transports = [
39
new winston.transports.Console(),
40
new winston.transports.File({
41
filename: 'logs/error.log',
42
level: 'error',
43
}),
44
new winston.transports.File({filename: 'logs/all.log'}),
45
];
46
47
const logger = winston.createLogger({
48
level: level(),
49
levels,
50
format,
51
transports,
52
});
53
54
export default logger;

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.

Create the src directory
mkdir src
cd src
mkdir bin config constants controllers env middlewares models repositories routers schemas security services validations

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

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.

src/constants/dateformat.constant.js
1
export const YYYY_MM_DD_HH_MM_SS_MS = 'YYYY-MM-DD HH:mm:ss:ms';
2
3
export default {
4
YYYY_MM_DD_HH_MM_SS_MS,
5
};
src/constants/http.code.constant.js
1
export const CONTINUE = 100;
2
export const SWITCHING_PROTOCOLS = 101;
3
export const PROCESSING = 102;
4
export const OK = 200;
5
export const CREATED = 201;
6
export const ACCEPTED = 202;
7
export const NON_AUTHORITATIVE_INFORMATION = 203;
8
export const NO_CONTENT = 204;
9
export const RESET_CONTENT = 205;
10
export const PARTIAL_CONTENT = 206;
11
export const MULTI_STATUS = 207;
12
export const ALREADY_REPORTED = 208;
13
export const IM_USED = 226;
14
export const MULTIPLE_CHOICES = 300;
15
export const MOVED_PERMANENTLY = 301;
16
export const MOVED_TEMPORARILY = 302;
17
export const SEE_OTHER = 303;
18
export const NOT_MODIFIED = 304;
19
export const USE_PROXY = 305;
20
export const SWITCH_PROXY = 306;
21
export const TEMPORARY_REDIRECT = 307;
22
export const BAD_REQUEST = 400;
23
export const UNAUTHORIZED = 401;
24
export const PAYMENT_REQUIRED = 402;
25
export const FORBIDDEN = 403;
26
export const NOT_FOUND = 404;
27
export const METHOD_NOT_ALLOWED = 405;
28
export const NOT_ACCEPTABLE = 406;
29
export const PROXY_AUTHENTICATION_REQUIRED = 407;
30
export const REQUEST_TIMEOUT = 408;
31
export const CONFLICT = 409;
32
export const GONE = 410;
33
export const LENGTH_REQUIRED = 411;
34
export const PRECONDITION_FAILED = 412;
35
export const PAYLOAD_TOO_LARGE = 413;
36
export const REQUEST_URI_TOO_LONG = 414;
37
export const UNSUPPORTED_MEDIA_TYPE = 415;
38
export const REQUESTED_RANGE_NOT_SATISFIABLE = 416;
39
export const EXPECTATION_FAILED = 417;
40
export const IM_A_TEAPOT = 418;
41
export const METHOD_FAILURE = 420;
42
export const MISDIRECTED_REQUEST = 421;
43
export const UNPROCESSABLE_ENTITY = 422;
44
export const LOCKED = 423;
45
export const FAILED_DEPENDENCY = 424;
46
export const UPGRADE_REQUIRED = 426;
47
export const PRECONDITION_REQUIRED = 428;
48
export const TOO_MANY_REQUESTS = 429;
49
export const REQUEST_HEADER_FIELDS_TOO_LARGE = 431;
50
export const UNAVAILABLE_FOR_LEGAL_REASONS = 451;
51
export const INTERNAL_SERVER_ERROR = 500;
52
export const NOT_IMPLEMENTED = 501;
53
export const BAD_GATEWAY = 502;
54
export const SERVICE_UNAVAILABLE = 503;
55
export const GATEWAY_TIMEOUT = 504;
56
export const HTTP_VERSION_NOT_SUPPORTED = 505;
57
export const VARIANT_ALSO_NEGOTIATES = 506;
58
export const INSUFFICIENT_STORAGE = 507;
59
export const LOOP_DETECTED = 508;
60
export const NOT_EXTENDED = 510;
61
export const NETWORK_AUTHENTICATION_REQUIRED = 511;
62
export const NETWORK_CONNECT_TIMEOUT_ERROR = 599;
63
64
export default {
65
CONTINUE,
66
SWITCHING_PROTOCOLS,
67
PROCESSING,
68
OK,
69
CREATED,
70
ACCEPTED,
71
NON_AUTHORITATIVE_INFORMATION,
72
NO_CONTENT,
73
RESET_CONTENT,
74
PARTIAL_CONTENT,
75
MULTI_STATUS,
76
ALREADY_REPORTED,
77
IM_USED,
78
MULTIPLE_CHOICES,
79
MOVED_PERMANENTLY,
80
MOVED_TEMPORARILY,
81
SEE_OTHER,
82
NOT_MODIFIED,
83
USE_PROXY,
84
SWITCH_PROXY,
85
TEMPORARY_REDIRECT,
86
BAD_REQUEST,
87
UNAUTHORIZED,
88
PAYMENT_REQUIRED,
89
FORBIDDEN,
90
NOT_FOUND,
91
METHOD_NOT_ALLOWED,
92
NOT_ACCEPTABLE,
93
PROXY_AUTHENTICATION_REQUIRED,
94
REQUEST_TIMEOUT,
95
CONFLICT,
96
GONE,
97
LENGTH_REQUIRED,
98
PRECONDITION_FAILED,
99
PAYLOAD_TOO_LARGE,
100
REQUEST_URI_TOO_LONG,
101
UNSUPPORTED_MEDIA_TYPE,
102
REQUESTED_RANGE_NOT_SATISFIABLE,
103
EXPECTATION_FAILED,
104
IM_A_TEAPOT,
105
METHOD_FAILURE,
106
MISDIRECTED_REQUEST,
107
UNPROCESSABLE_ENTITY,
108
LOCKED,
109
FAILED_DEPENDENCY,
110
UPGRADE_REQUIRED,
111
PRECONDITION_REQUIRED,
112
TOO_MANY_REQUESTS,
113
REQUEST_HEADER_FIELDS_TOO_LARGE,
114
UNAVAILABLE_FOR_LEGAL_REASONS,
115
INTERNAL_SERVER_ERROR,
116
NOT_IMPLEMENTED,
117
BAD_GATEWAY,
118
SERVICE_UNAVAILABLE,
119
GATEWAY_TIMEOUT,
120
HTTP_VERSION_NOT_SUPPORTED,
121
VARIANT_ALSO_NEGOTIATES,
122
INSUFFICIENT_STORAGE,
123
LOOP_DETECTED,
124
NOT_EXTENDED,
125
NETWORK_AUTHENTICATION_REQUIRED,
126
NETWORK_CONNECT_TIMEOUT_ERROR,
127
};
src/constants/http.reason.constant.js
1
export const CONTINUE = 'Continue';
2
export const SWITCHING_PROTOCOLS = 'Switching Protocols';
3
export const PROCESSING = 'Processing';
4
export const OK = 'OK';
5
export const CREATED = 'Created';
6
export const ACCEPTED = 'Accepted';
7
export const NON_AUTHORITATIVE_INFORMATION = 'Non-Authoritative Information';
8
export const NO_CONTENT = 'No Content';
9
export const RESET_CONTENT = 'Reset Content';
10
export const PARTIAL_CONTENT = 'Partial Content';
11
export const MULTI_STATUS = 'Multi-Status';
12
export const ALREADY_REPORTED = 'Already Reported';
13
export const IM_USED = 'IM Used';
14
export const MULTIPLE_CHOICES = 'Multiple Choices';
15
export const MOVED_PERMANENTLY = 'Moved Permanently';
16
export const MOVED_TEMPORARILY = 'Moved Temporarily';
17
export const SEE_OTHER = 'See Other';
18
export const NOT_MODIFIED = 'Not Modified';
19
export const USE_PROXY = 'Use Proxy';
20
export const SWITCH_PROXY = 'Switch Proxy';
21
export const TEMPORARY_REDIRECT = 'Temporary Redirect';
22
export const BAD_REQUEST = 'Bad Request';
23
export const UNAUTHORIZED = 'Unauthorized';
24
export const PAYMENT_REQUIRED = 'Payment Required';
25
export const FORBIDDEN = 'Forbidden';
26
export const NOT_FOUND = 'Not Found';
27
export const METHOD_NOT_ALLOWED = 'Method Not Allowed';
28
export const NOT_ACCEPTABLE = 'Not Acceptable';
29
export const PROXY_AUTHENTICATION_REQUIRED = 'Proxy Authentication Required';
30
export const REQUEST_TIMEOUT = 'Request Timeout';
31
export const CONFLICT = 'Conflict';
32
export const GONE = 'Gone';
33
export const LENGTH_REQUIRED = 'Length Required';
34
export const PRECONDITION_FAILED = 'Precondition Failed';
35
export const PAYLOAD_TOO_LARGE = 'Payload Too Large';
36
export const REQUEST_URI_TOO_LONG = 'Request URI Too Long';
37
export const UNSUPPORTED_MEDIA_TYPE = 'Unsupported Media Type';
38
export const REQUESTED_RANGE_NOT_SATISFIABLE =
39
'Requested Range Not Satisfiable';
40
export const EXPECTATION_FAILED = 'Expectation Failed';
41
export const IM_A_TEAPOT = "I'm a teapot";
42
export const METHOD_FAILURE = 'Method Failure';
43
export const MISDIRECTED_REQUEST = 'Misdirected Request';
44
export const UNPROCESSABLE_ENTITY = 'Unprocessable Entity';
45
export const LOCKED = 'Locked';
46
export const FAILED_DEPENDENCY = 'Failed Dependency';
47
export const UPGRADE_REQUIRED = 'Upgrade Required';
48
export const PRECONDITION_REQUIRED = 'Precondition Required';
49
export const TOO_MANY_REQUESTS = 'Too Many Requests';
50
export const REQUEST_HEADER_FIELDS_TOO_LARGE =
51
'Request Header Fields Too Large';
52
export const UNAVAILABLE_FOR_LEGAL_REASONS = 'Unavailable For Legal Reasons';
53
export const INTERNAL_SERVER_ERROR = 'Internal Server Error';
54
export const NOT_IMPLEMENTED = 'Not Implemented';
55
export const BAD_GATEWAY = 'Bad Gateway';
56
export const SERVICE_UNAVAILABLE = 'Service Unavailable';
57
export const GATEWAY_TIMEOUT = 'Gateway Timeout';
58
export const HTTP_VERSION_NOT_SUPPORTED = 'HTTP Version Not Supported';
59
export const VARIANT_ALSO_NEGOTIATES = 'Variant Also Negotiates';
60
export const INSUFFICIENT_STORAGE = 'Insufficient Storage';
61
export const LOOP_DETECTED = 'Loop Detected';
62
export const NOT_EXTENDED = 'Not Extended';
63
export const NETWORK_AUTHENTICATION_REQUIRED =
64
'Network Authentication Required';
65
export const NETWORK_CONNECT_TIMEOUT_ERROR = 'Network Connect Timeout Error';
66
67
export default {
68
CONTINUE,
69
SWITCHING_PROTOCOLS,
70
PROCESSING,
71
OK,
72
CREATED,
73
ACCEPTED,
74
NON_AUTHORITATIVE_INFORMATION,
75
NO_CONTENT,
76
RESET_CONTENT,
77
PARTIAL_CONTENT,
78
MULTI_STATUS,
79
ALREADY_REPORTED,
80
IM_USED,
81
MULTIPLE_CHOICES,
82
MOVED_PERMANENTLY,
83
MOVED_TEMPORARILY,
84
SEE_OTHER,
85
NOT_MODIFIED,
86
USE_PROXY,
87
SWITCH_PROXY,
88
TEMPORARY_REDIRECT,
89
BAD_REQUEST,
90
UNAUTHORIZED,
91
PAYMENT_REQUIRED,
92
FORBIDDEN,
93
NOT_FOUND,
94
METHOD_NOT_ALLOWED,
95
NOT_ACCEPTABLE,
96
PROXY_AUTHENTICATION_REQUIRED,
97
REQUEST_TIMEOUT,
98
CONFLICT,
99
GONE,
100
LENGTH_REQUIRED,
101
PRECONDITION_FAILED,
102
PAYLOAD_TOO_LARGE,
103
REQUEST_URI_TOO_LONG,
104
UNSUPPORTED_MEDIA_TYPE,
105
REQUESTED_RANGE_NOT_SATISFIABLE,
106
EXPECTATION_FAILED,
107
IM_A_TEAPOT,
108
METHOD_FAILURE,
109
MISDIRECTED_REQUEST,
110
UNPROCESSABLE_ENTITY,
111
LOCKED,
112
FAILED_DEPENDENCY,
113
UPGRADE_REQUIRED,
114
PRECONDITION_REQUIRED,
115
TOO_MANY_REQUESTS,
116
REQUEST_HEADER_FIELDS_TOO_LARGE,
117
UNAVAILABLE_FOR_LEGAL_REASONS,
118
INTERNAL_SERVER_ERROR,
119
NOT_IMPLEMENTED,
120
BAD_GATEWAY,
121
SERVICE_UNAVAILABLE,
122
GATEWAY_TIMEOUT,
123
HTTP_VERSION_NOT_SUPPORTED,
124
VARIANT_ALSO_NEGOTIATES,
125
INSUFFICIENT_STORAGE,
126
LOOP_DETECTED,
127
NOT_EXTENDED,
128
NETWORK_AUTHENTICATION_REQUIRED,
129
NETWORK_CONNECT_TIMEOUT_ERROR,
130
};
src/constants/path.constant.js
1
export const LOGS_ALL = 'logs/all.log';
2
export const LOGS_ERROR = 'logs/error.log';
3
4
export default {
5
LOGS_ALL,
6
LOGS_ERROR,
7
};

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.
Create the .env files
cd ..
touch .env .env.example

Now we add a new variable to .env:

.env
NODE_ENV=development
# NODE_ENV=production
PORT=3030
DATABASE_URL=mongodb://127.0.0.1:27017/example
.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.

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:

src/config/db.config.js
1
import {connect} from 'mongoose';
2
import logger from '../utils/logger.util';
3
4
const connectDb = async (URL) => {
5
const connectionParams = {
6
useNewUrlParser: true,
7
useUnifiedTopology: true,
8
};
9
10
try {
11
const connection = await connect(URL, connectionParams);
12
logger.info(`Mongo DB is connected to: ${connection.connection.host}`);
13
} catch (err) {
14
logger.error(`An error ocurred\n\r\n\r${err}`);
15
}
16
};
17
18
export default connectDb;
src/env/variable.env.js
1
import dotenv from 'dotenv';
2
3
dotenv.config();
4
5
export const {NODE_ENV} = process.env;
6
export const {PORT} = process.env;
7
export const {DATABASE_URL} = process.env;
8
9
export default {
10
NODE_ENV,
11
PORT,
12
DATABASE_URL,
13
};
src/index.js
1
import express from 'express';
2
import cors from 'cors';
3
4
import {DATABASE_URL} from './env/variable.env';
5
import connectDb from './config/db.config';
6
7
//http constant
8
import ConstantHttpCode from './constants/http.code.constant';
9
import ConstantHttpReason from './constants/http.reason.constant';
10
11
connectDb(DATABASE_URL);
12
13
const app = express();
14
15
app.use(express.urlencoded({extended: true}));
16
app.use(express.json());
17
app.use(cors());
18
19
app.get('/', (req, res, next) => {
20
try {
21
res.status(ConstantHttpCode.OK).json({
22
status: {
23
code: ConstantHttpCode.OK,
24
msg: ConstantHttpReason.OK,
25
},
26
API: 'Work',
27
});
28
} catch (err) {
29
next(err);
30
}
31
});
32
33
export default app;
src/bin/www.js
1
#!/user/bin/env node
2
3
import http from 'http';
4
import app from '..';
5
6
import {PORT} from '../env/variable.env';
7
import logger from '../utils/logger.util';
8
9
/**
10
* Normalize a port into a number, string, or false.
11
*/
12
const normalizePort = (val) => {
13
const port = parseInt(val, 10);
14
15
if (Number.isNaN(port)) {
16
// named pipe
17
return val;
18
}
19
20
if (port >= 0) {
21
// port number
22
return port;
23
}
24
25
return false;
26
};
27
28
const port = normalizePort(PORT || '3000');
29
app.set('port', port);
30
31
/**
32
* Create HTTP server.
33
*/
34
const server = http.createServer(app);
35
36
/**
37
* Event listener for HTTP server "error" event.
38
*/
39
const onError = (error) => {
40
if (error.syscall !== 'listen') {
41
throw error;
42
}
43
44
const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;
45
46
// handle specific listen errors with friendly messages
47
switch (error.code) {
48
case 'EACCES':
49
logger.error(`${bind} requires elevated privileges`);
50
process.exit(1);
51
break;
52
case 'EADDRINUSE':
53
logger.error(`${bind} is already in use`);
54
process.exit(1);
55
break;
56
default:
57
throw error;
58
}
59
};
60
61
/**
62
* Event listener for HTTP server "listening" event.
63
*/
64
const onListening = () => {
65
const addr = server.address();
66
const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
67
logger.info(`Listening on ${bind}`);
68
};
69
70
server.listen(port);
71
server.on('error', onError);
72
server.on('listening', onListening);

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

package.json
6 collapsed lines
1
{
2
"name": "tutorial",
3
"version": "0.0.0",
4
"description": "",
5
"keywords": [],
6
"main": "index.js",
7
"scripts": {
8
"start": "node build/bin/www.js",
9
"dev": "nodemon --exec babel-node src/bin/www.js",
10
"clean": "rm -rf build",
11
"build": "yarn clean && npx babel src -d build --minified --presets @babel/preset-env",
12
"lint": "eslint \"src/**/*.js\" --fix",
13
"lint:check": "eslint \"src/**/*.js\"",
14
"prettier": "prettier --write \"src/**/*.js\"",
15
"prettier:check": "prettier --check \"src/**/*.js\"",
16
"prepare": "husky install"
17
},
49 collapsed lines
18
"license": "MIT",
19
"author": {
20
"name": "YOUR_NAME",
21
"email": "YOUR_EMAIL",
22
"url": "YOUR_WEBSITE"
23
},
24
"homepage": "YOUR_GIT_REPO_URL#readme",
25
"repository": {
26
"type": "git",
27
"url": "git+YOUR_GIT_REPO_URL.git"
28
},
29
"bugs": {
30
"url": "YOUR_GIT_REPO_URL/issues"
31
},
32
"engines": {
33
"node": ">=14.0.0",
34
"yarn": ">=1.20.0",
35
"npm": "please-use-yarn"
36
},
37
"dependencies": {
38
"@babel/core": "7.18.5",
39
"@babel/node": "7.18.5",
40
"@babel/preset-env": "7.18.2",
41
"compression": "1.7.4",
42
"cookie-parser": "1.4.6",
43
"core-js": "3.23.2",
44
"cors": "2.8.5",
45
"crypto-js": "4.1.1",
46
"dotenv": "16.0.1",
47
"express": "4.18.1",
48
"helmet": "5.1.0",
49
"husky": "8.0.1",
50
"jsonwebtoken": "9.0.0",
51
"mongoose": "6.11.3",
52
"regenerator-runtime": "0.13.9",
53
"winston": "3.8.0"
54
},
55
"devDependencies": {
56
"@babel/cli": "7.17.10",
57
"@commitlint/cli": "17.0.3",
58
"@commitlint/config-conventional": "17.0.3",
59
"babel-plugin-module-resolver": "4.1.0",
60
"eslint": "8.18.0",
61
"eslint-config-airbnb-base": "15.0.0",
62
"eslint-config-prettier": "8.5.0",
63
"eslint-plugin-import": "2.26.0",
64
"eslint-plugin-prettier": "4.0.0",
65
"prettier": "2.7.1"
66
}
67
}

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

Run the application
yarn dev
yarn start
yarn build

New we’ll add a new 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.

Install compression
yarn add compression

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.

Install cookie-parser
yarn add cookie-parser

core-js: Your Node.js app’s main file contains middleware for core-js. This middleware will add the necessary polyfills to your application.

Install core-js
yarn add core-js

helmet: Your Node.js app’s main file contains middleware for helmet. This middleware will add security headers to your application.

Install helmet
yarn add helmet

regenerator-runtime: Your Node.js app’s main file contains middleware for regenerator-runtime. This middleware will add the necessary polyfills to your application.

Install regenerator-runtime
yarn add regenerator-runtime

we’ll change the index.js file:

src/index.js
1
import express from 'express';
2
import helmet from 'helmet';
3
import cors from 'cors';
4
import compression from 'compression';
5
import cookieParser from 'cookie-parser';
6
7
import {DATABASE_URL} from './env/variable.env';
8
import connectDb from './config/db.config';
9
10
//http constant
11
import ConstantHttpCode from './constants/http.code.constant';
12
import ConstantHttpReason from './constants/http.reason.constant';
13
14
connectDb(DATABASE_URL);
15
16
const app = express();
17
18
//helmet
19
app.use(helmet());
20
21
app.use(express.urlencoded({extended: true}));
22
app.use(express.json());
23
app.use(compression());
24
app.use(cors());
25
app.use(cookieParser());
26
27
app.get('/', (req, res, next) => {
28
try {
29
res.status(ConstantHttpCode.OK).json({
30
status: {
31
code: ConstantHttpCode.OK,
32
msg: ConstantHttpReason.OK,
33
},
34
API: 'Work',
35
});
36
} catch (err) {
37
next(err);
38
}
39
});
40
41
export default app;

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

src/bin/www.js
1
#!/user/bin/env node
2
import 'core-js/stable';
3
import 'regenerator-runtime/runtime';
4
5
import http from 'http';
6
import app from '..';
7
8
import {PORT} from '../env/variable.env';
9
import logger from '../utils/logger.util';
10
11
/**
12
* Normalize a port into a number, string, or false.
13
*/
14
const normalizePort = (val) => {
15
const port = parseInt(val, 10);
16
17
if (Number.isNaN(port)) {
18
// named pipe
19
return val;
20
}
21
22
if (port >= 0) {
23
// port number
24
return port;
25
}
26
27
return false;
28
};
29
30
const port = normalizePort(PORT || '3000');
31
app.set('port', port);
32
33
/**
34
* Create HTTP server.
35
*/
36
const server = http.createServer(app);
37
38
/**
39
* Event listener for HTTP server "error" event.
40
*/
41
const onError = (error) => {
42
if (error.syscall !== 'listen') {
43
throw error;
44
}
45
46
const bind = typeof port === 'string' ? `Pipe ${port}` : `Port ${port}`;
47
48
// handle specific listen errors with friendly messages
49
switch (error.code) {
50
case 'EACCES':
51
logger.error(`${bind} requires elevated privileges`);
52
process.exit(1);
53
break;
54
case 'EADDRINUSE':
55
logger.error(`${bind} is already in use`);
56
process.exit(1);
57
break;
58
default:
59
throw error;
60
}
61
};
62
63
/**
64
* Event listener for HTTP server "listening" event.
65
*/
66
const onListening = () => {
67
const addr = server.address();
68
const bind = typeof addr === 'string' ? `pipe ${addr}` : `port ${addr.port}`;
69
logger.info(`Listening on ${bind}`);
70
};
71
72
server.listen(port);
73
server.on('error', onError);
74
server.on('listening', onListening);

new we’ll run the application with yarn dev:

Run the application
╭─mkabumattar@mkabumattar in ~/work/tutorial is v0.0.0 via v18.3.0 took 243ms
╰─λ yarn dev
yarn run v1.22.18
$ nodemon --exec babel-node src/bin/www.js
[nodemon] 2.0.16
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `babel-node 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.

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

Install Husky
yarn add husky
git init
npx 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.

Create the .gitignore file
touch .gitignore
.gitignore
1
# Logs
2
logs
3
*.log
4
npm-debug.log*
5
yarn-debug.log*
6
yarn-error.log*
7
lerna-debug.log*
8
.pnpm-debug.log*
9
10
# Diagnostic reports (https://nodejs.org/api/report.html)
11
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
12
13
# Runtime data
14
pids
15
*.pid
16
*.seed
17
*.pid.lock
18
19
# Directory for instrumented libs generated by jscoverage/JSCover
20
lib-cov
21
22
# Coverage directory used by tools like istanbul
23
coverage
24
*.lcov
25
26
# nyc test coverage
27
.nyc_output
28
29
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
30
.grunt
31
32
# Bower dependency directory (https://bower.io/)
33
bower_components
34
35
# node-waf configuration
36
.lock-wscript
37
38
# Compiled binary addons (https://nodejs.org/api/addons.html)
39
build
40
build/Release
41
42
# Dependency directories
43
node_modules/
44
jspm_packages/
45
46
# Snowpack dependency directory (https://snowpack.dev/)
47
web_modules/
48
49
# TypeScript cache
50
*.tsbuildinfo
51
52
# Optional npm cache directory
53
.npm
54
55
# Optional eslint cache
56
.eslintcache
57
58
# Optional stylelint cache
59
.stylelintcache
60
61
# Microbundle cache
62
.rpt2_cache/
63
.rts2_cache_cjs/
64
.rts2_cache_es/
65
.rts2_cache_umd/
66
67
# Optional REPL history
68
.node_repl_history
69
70
# Output of 'npm pack'
71
*.tgz
72
73
# Yarn Integrity file
74
.yarn-integrity
75
76
# dotenv environment variable files
77
.env
78
.env.development.local
79
.env.test.local
80
.env.production.local
81
.env.local
82
83
# parcel-bundler cache (https://parceljs.org/)
84
.cache
85
.parcel-cache
86
87
# vuepress build output
88
.vuepress/dist
89
90
# vuepress v2.x temp and cache directory
91
.temp
92
.cache
93
94
# Docusaurus cache and generated files
95
.docusaurus
96
97
# Serverless directories
98
.serverless/
99
100
# FuseBox cache
101
.fusebox/
102
103
# DynamoDB Local files
104
.dynamodb/
105
106
# TernJS port file
107
.tern-port
108
109
# Stores VSCode versions used for testing VSCode extensions
110
.vscode-test
111
112
# yarn v2
113
.yarn/cache
114
.yarn/unplugged
115
.yarn/build-state.yml
116
.yarn/install-state.gz
117
.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:

package.json
6 collapsed lines
1
{
2
"name": "tutorial",
3
"version": "0.0.0",
4
"description": "",
5
"keywords": [],
6
"main": "index.js",
7
"scripts": {
8 collapsed lines
8
"start": "node build/bin/www.js",
9
"dev": "nodemon --exec babel-node src/bin/www.js",
10
"clean": "rm -rf build",
11
"build": "yarn clean && npx babel src -d build --minified --presets @babel/preset-env",
12
"lint": "eslint \"src/**/*.js\" --fix",
13
"lint:check": "eslint \"src/**/*.js\"",
14
"prettier": "prettier --write \"src/**/*.js\"",
15
"prettier:check": "prettier --check \"src/**/*.js\"",
16
"prepare": "husky install"
17
},
49 collapsed lines
18
"license": "MIT",
19
"author": {
20
"name": "YOUR_NAME",
21
"email": "YOUR_EMAIL",
22
"url": "YOUR_WEBSITE"
23
},
24
"homepage": "YOUR_GIT_REPO_URL#readme",
25
"repository": {
26
"type": "git",
27
"url": "git+YOUR_GIT_REPO_URL.git"
28
},
29
"bugs": {
30
"url": "YOUR_GIT_REPO_URL/issues"
31
},
32
"engines": {
33
"node": ">=14.0.0",
34
"yarn": ">=1.20.0",
35
"npm": "please-use-yarn"
36
},
37
"dependencies": {
38
"@babel/core": "7.18.5",
39
"@babel/node": "7.18.5",
40
"@babel/preset-env": "7.18.2",
41
"compression": "1.7.4",
42
"cookie-parser": "1.4.6",
43
"core-js": "3.23.2",
44
"cors": "2.8.5",
45
"crypto-js": "4.1.1",
46
"dotenv": "16.0.1",
47
"express": "4.18.1",
48
"helmet": "5.1.0",
49
"husky": "8.0.1",
50
"jsonwebtoken": "9.0.0",
51
"mongoose": "6.11.3",
52
"regenerator-runtime": "0.13.9",
53
"winston": "3.8.0"
54
},
55
"devDependencies": {
56
"@babel/cli": "7.17.10",
57
"@commitlint/cli": "17.0.3",
58
"@commitlint/config-conventional": "17.0.3",
59
"babel-plugin-module-resolver": "4.1.0",
60
"eslint": "8.18.0",
61
"eslint-config-airbnb-base": "15.0.0",
62
"eslint-config-prettier": "8.5.0",
63
"eslint-plugin-import": "2.26.0",
64
"eslint-plugin-prettier": "4.0.0",
65
"prettier": "2.7.1"
66
}
67
}

This will ensure Husky gets installed automatically when other developers run the project.

To create a hook run:

Create a hook
npx husky add .husky/pre-commit "yarn lint"

The aforementioned states that the yarn 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:

Create a hook
npx husky add .husky/pre-push "yarn 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.

Install Commitlint
yarn 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:

Create a commitlint.config.js file
touch commitlint.config.js
commitlint.config.js
1
// build: Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm)
2
// ci: Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs)
3
// docs: Documentation only changes
4
// feat: A new feature
5
// fix: A bug fix
6
// perf: A code change that improves performance
7
// refactor: A code change that neither fixes a bug nor adds a feature
8
// style: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc)
9
// test: Adding missing tests or correcting existing tests
10
11
module.exports = {
12
extends: ['@commitlint/config-conventional'],
13
rules: {
14
'body-leading-blank': [1, 'always'],
15
'body-max-line-length': [2, 'always', 100],
16
'footer-leading-blank': [1, 'always'],
17
'footer-max-line-length': [2, 'always', 100],
18
'header-max-length': [2, 'always', 100],
19
'scope-case': [2, 'always', 'lower-case'],
20
'subject-case': [
21
2,
22
'never',
23
['sentence-case', 'start-case', 'pascal-case', 'upper-case'],
24
],
25
'subject-empty': [2, 'never'],
26
'subject-full-stop': [2, 'never', '.'],
27
'type-case': [2, 'always', 'lower-case'],
28
'type-empty': [2, 'never'],
29
'type-enum': [
30
2,
31
'always',
32
[
33
'build',
34
'chore',
35
'ci',
36
'docs',
37
'feat',
38
'fix',
39
'perf',
40
'refactor',
41
'revert',
42
'style',
43
'test',
44
'translation',
45
'security',
46
'changeset',
47
],
48
],
49
},
50
};

Afterward, use Husky to enable commitlint by using:

Create a hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

Know connected this repository with GitHub, you can now push your commits to GitHub.

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:

Create the settings.json file
mkdir .vscode
touch .vscode/settings.json

settings.json

"./
1
{
2
"editor.defaultFormatter": "esbenp.prettier-vscode",
3
"editor.formatOnSave": true,
4
"editor.codeActionsOnSave": {
5
"source.fixAll": true,
6
"source.organizeImports": true
7
}
8
}

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:

Terminal window
touch .vscode/launch.json
.vscode/launch.json
1
{
2
"version": "0.1.0",
3
"configurations": [
4
{
5
"name": "debug server",
6
"type": "node-terminal",
7
"request": "launch",
8
"command": "yarn dev"
9
}
10
]
11
}

Authentication

Authentication Setup

We’ll add a new packages to our project:

  • crypto-js: A JavaScript library for encryption and decryption.
  • jsonwebtoken: A JavaScript library for creating and verifying JSON Web Tokens.
Install crypto-js and jsonwebtoken
yarn add crypto-js jsonwebtoken

add secret key to .env file:

.env
...
JWT_SECRET=secret
PASS_SECRET=secret

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

src/env/variable.env.js
1
...
2
3
export const { JWT_SECRET } = process.env
4
export const { PASS_SECRET } = process.env
5
6
export default {
7
...,
8
JWT_SECRET,
9
PASS_SECRET,
10
}

know we’ll add the constants to:

src/constants/api.constant.js
1
// api
2
export const API_AUTH = '/api/auth';
3
export const API_USERS = '/api/users';
4
5
// auth
6
export const AUTH_REGISTER = '/register';
7
export const AUTH_LOGIN = '/login';
8
9
// users
10
export const USER_UPDATE_USERNAME = '/update-username/:id';
11
export const USER_UPDATE_NAME = '/update-name/:id';
12
export const USER_UPDATE_EMAIL = '/update-email/:id';
13
export const USER_UPDATE_PASSWORD = '/update-password/:id';
14
export const USER_UPDATE_PHONE = '/update-phone/:id';
15
export const USER_UPDATE_ADDRESS = '/update-address/:id';
16
export const USER_DELETE = '/delete/:id';
17
export const USER_GET = '/find/:id';
18
export const USER_GET_ALL = '/';
19
export const USER_GET_ALL_STATS = '/stats';
20
21
export default {
22
// api
23
API_AUTH,
24
API_USERS,
25
26
// auth
27
AUTH_REGISTER,
28
AUTH_LOGIN,
29
30
// users
31
USER_UPDATE_USERNAME,
32
USER_UPDATE_NAME,
33
USER_UPDATE_EMAIL,
34
USER_UPDATE_PASSWORD,
35
USER_UPDATE_PHONE,
36
USER_UPDATE_ADDRESS,
37
USER_DELETE,
38
USER_GET,
39
USER_GET_ALL,
40
USER_GET_ALL_STATS,
41
};
src/constants/message.constant.js
1
// token
2
export const TOKEN_NOT_VALID = 'Token not valid';
3
export const NOT_AUTHENTICATED = 'Not authenticated';
4
export const NOT_ALLOWED = 'Not allowed';
5
6
// auth
7
export const USERNAME_NOT_VALID = 'username is not valid';
8
export const NAME_NOT_VALID = 'name is not valid';
9
export const EMAIL_NOT_VALID = 'email is not valid';
10
export const PASSWORD_NOT_VALID = 'password is not valid';
11
export const PHONE_NOT_VALID = 'phone is not valid';
12
export const ADDRESS_NOT_VALID = 'address is not valid';
13
export const USERNAME_EXIST = 'username is exist';
14
export const EMAIL_EXIST = 'email is exist';
15
export const PHONE_EXIST = 'phone is exist';
16
export const USER_NOT_CREATE = 'user is not create, please try again';
17
export const USER_CREATE_SUCCESS = 'user is create success, please login';
18
export const USER_NOT_FOUND = 'user is not found';
19
export const PASSWORD_NOT_MATCH = 'password is not match';
20
export const USER_LOGIN_SUCCESS = 'user is login success';
21
22
// user
23
export const USERNAME_NOT_CHANGE = 'username is not change';
24
export const USERNAME_CHANGE_SUCCESS = 'username is change success';
25
export const NAME_NOT_CHANGE = 'name is not change';
26
export const NAME_CHANGE_SUCCESS = 'name is change success';
27
export const EMAIL_NOT_CHANGE = 'email is not change';
28
export const EMAIL_CHANGE_SUCCESS = 'email is change success';
29
export const PASSWORD_NOT_CHANGE = 'password is not change';
30
export const PASSWORD_CHANGE_SUCCESS = 'password is change success';
31
export const PHONE_NOT_CHANGE = 'phone is not change';
32
export const PHONE_CHANGE_SUCCESS = 'phone is change success';
33
export const ADDRESS_NOT_CHANGE = 'address is not change';
34
export const ADDRESS_CHANGE_SUCCESS = 'address is change success';
35
export const USER_NOT_DELETE = 'user is not delete, please try again';
36
export const USER_DELETE_SUCCESS = 'user is delete success';
37
export const USER_FOUND = 'user is found';
38
39
export default {
40
// token
41
TOKEN_NOT_VALID,
42
NOT_AUTHENTICATED,
43
NOT_ALLOWED,
44
45
// auth
46
USERNAME_NOT_VALID,
47
NAME_NOT_VALID,
48
EMAIL_NOT_VALID,
49
PASSWORD_NOT_VALID,
50
PHONE_NOT_VALID,
51
ADDRESS_NOT_VALID,
52
USERNAME_EXIST,
53
EMAIL_EXIST,
54
PHONE_EXIST,
55
USER_NOT_CREATE,
56
USER_CREATE_SUCCESS,
57
USER_NOT_FOUND,
58
PASSWORD_NOT_MATCH,
59
USER_LOGIN_SUCCESS,
60
61
// user
62
USERNAME_NOT_CHANGE,
63
USERNAME_CHANGE_SUCCESS,
64
NAME_NOT_CHANGE,
65
NAME_CHANGE_SUCCESS,
66
EMAIL_NOT_CHANGE,
67
EMAIL_CHANGE_SUCCESS,
68
PASSWORD_NOT_CHANGE,
69
PASSWORD_CHANGE_SUCCESS,
70
PHONE_NOT_CHANGE,
71
PHONE_CHANGE_SUCCESS,
72
ADDRESS_NOT_CHANGE,
73
ADDRESS_CHANGE_SUCCESS,
74
USER_NOT_DELETE,
75
USER_DELETE_SUCCESS,
76
USER_FOUND,
77
};
src/constants/model.constant.js
1
export const USER_MODEL = 'UserModel';
2
3
export default {
4
USER_MODEL,
5
};
src/constants/number.constant.js
1
// user
2
export const USERNAME_MIN_LENGTH = 3;
3
export const USERNAME_MAX_LENGTH = 20;
4
export const NAME_MIN_LENGTH = 3;
5
export const NAME_MAX_LENGTH = 80;
6
export const EMAIL_MAX_LENGTH = 50;
7
export const PASSWORD_MIN_LENGTH = 8;
8
export const PHONE_MIN_LENGTH = 10;
9
export const PHONE_MAX_LENGTH = 20;
10
export const ADDRESS_MIN_LENGTH = 10;
11
export const ADDRESS_MAX_LENGTH = 200;
12
13
export default {
14
USERNAME_MIN_LENGTH,
15
USERNAME_MAX_LENGTH,
16
NAME_MIN_LENGTH,
17
NAME_MAX_LENGTH,
18
EMAIL_MAX_LENGTH,
19
PASSWORD_MIN_LENGTH,
20
PHONE_MIN_LENGTH,
21
PHONE_MAX_LENGTH,
22
ADDRESS_MIN_LENGTH,
23
ADDRESS_MAX_LENGTH,
24
};
src/constants/regex.constant.js
1
export const USERNAME = /^(?!.*\.\.)(?!.*\.$)[^\W][\w.]{3,32}$/;
2
export const EMAIL =
3
/^(([^<>()\\[\]\\.,;:\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,}))$/;
4
export const PASSWORD =
5
/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/;
6
export const NAME = /^[a-zA-Z ]{2,35}$/;
7
export const PHONE =
8
/^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$/gm;
9
export const ADDRESS = /^[a-zA-Z0-9\s,'-]{10,200}$/;
10
11
export default {
12
USERNAME,
13
EMAIL,
14
PASSWORD,
15
NAME,
16
PHONE,
17
ADDRESS,
18
};

Authentication Middleware

src/middlewares/token.middleware.js
1
import jwt from 'jsonwebtoken';
2
3
import ConstantMessage from '../constants/message.constant';
4
import {JWT_SECRET} from '../env/variable.env';
5
6
// http constant
7
import ConstantHttpCode from '../constants/http.code.constant';
8
import ConstantHttpReason from '../constants/http.reason.constant';
9
10
// logger
11
import logger from '../utils/logger.util';
12
13
export const verifyToken = (req, res, next) => {
14
const authHeader = req.headers.token;
15
logger.info(`authHeader: ${authHeader}`);
16
if (authHeader) {
17
const token = authHeader.split(' ')[1];
18
return jwt.verify(token, JWT_SECRET, (err, user) => {
19
if (err) {
20
res.status(ConstantHttpCode.FORBIDDEN).json({
21
status: {
22
code: ConstantHttpCode.FORBIDDEN,
23
msg: ConstantHttpReason.FORBIDDEN,
24
},
25
msg: ConstantMessage.TOKEN_NOT_VALID,
26
});
27
}
28
req.user = user;
29
return next();
30
});
31
}
32
33
return res.status(ConstantHttpCode.UNAUTHORIZED).json({
34
status: {
35
code: ConstantHttpCode.UNAUTHORIZED,
36
msg: ConstantHttpReason.UNAUTHORIZED,
37
},
38
msg: ConstantMessage.NOT_AUTHENTICATED,
39
});
40
};
41
42
export const verifyTokenAndAuthorization = (req, res, next) => {
43
verifyToken(req, res, () => {
44
if (req.user.id === req.params.id || req.user.isAdmin) {
45
return next();
46
}
47
48
return res.status(ConstantHttpCode.FORBIDDEN).json({
49
status: {
50
code: ConstantHttpCode.FORBIDDEN,
51
msg: ConstantHttpReason.FORBIDDEN,
52
},
53
msg: ConstantMessage.NOT_ALLOWED,
54
});
55
});
56
};
57
58
export const verifyTokenAndAdmin = (req, res, next) => {
59
verifyToken(req, res, () => {
60
if (req.user.isAdmin) {
61
return next();
62
}
63
64
return res.status(ConstantHttpCode.FORBIDDEN).json({
65
status: {
66
code: ConstantHttpCode.FORBIDDEN,
67
msg: ConstantHttpReason.FORBIDDEN,
68
},
69
msg: ConstantMessage.NOT_ALLOWED,
70
});
71
});
72
};
73
74
export default {
75
verifyToken,
76
verifyTokenAndAuthorization,
77
verifyTokenAndAdmin,
78
};

Authentication Security

src/security/user.security.js
1
import CryptoJS from 'crypto-js';
2
import jwt from 'jsonwebtoken';
3
4
import {JWT_SECRET, PASS_SECRET} from '../env/variable.env';
5
6
export const encryptedPassword = (password) => {
7
return CryptoJS.AES.encrypt(password, PASS_SECRET).toString();
8
};
9
10
export const decryptedPassword = (password) => {
11
return CryptoJS.AES.decrypt(password, PASS_SECRET).toString(
12
CryptoJS.enc.Utf8,
13
);
14
};
15
16
export const comparePassword = (password, dPassword) => {
17
const compare = decryptedPassword(dPassword) === password;
18
return compare;
19
};
20
21
export const generateAccessToken = (id, isAdmin) => {
22
return jwt.sign({id, isAdmin}, JWT_SECRET, {expiresIn: '3d'});
23
};
24
25
export default {
26
encryptedPassword,
27
decryptedPassword,
28
comparePassword,
29
generateAccessToken,
30
};

Authentication validations

src/validations/user.validation.js
1
import ConstantRegex from '../constants/regex.constant';
2
3
export const validateUsername = async (username) => {
4
return ConstantRegex.USERNAME.test(username);
5
};
6
7
export const validateName = async (name) => {
8
return ConstantRegex.NAME.test(name);
9
};
10
11
export const validateEmail = async (email) => {
12
return ConstantRegex.EMAIL.test(email);
13
};
14
15
export const validatePassword = async (password) => {
16
return ConstantRegex.PASSWORD.test(password);
17
};
18
19
export const validatePhone = async (phone) => {
20
return ConstantRegex.PHONE.test(phone);
21
};
22
23
export const validateAddress = async (address) => {
24
return ConstantRegex.ADDRESS.test(address);
25
};
26
27
export default {
28
validateUsername,
29
validateName,
30
validateEmail,
31
validatePassword,
32
validatePhone,
33
validateAddress,
34
};

Authentication schemas

src/schemas/user.schema.js
1
import mongoose from 'mongoose';
2
import ConstantNumber from '../constants/number.constant';
3
4
const UserSchema = new mongoose.Schema(
5
{
6
username: {
7
type: String,
8
required: true,
9
unique: true,
10
min: ConstantNumber.USERNAME_MIN_LENGTH,
11
max: ConstantNumber.USERNAME_MAX_LENGTH,
12
},
13
name: {
14
type: String,
15
required: true,
16
min: ConstantNumber.NAME_MIN_LENGTH,
17
max: ConstantNumber.NAME_MAX_LENGTH,
18
},
19
email: {
20
type: String,
21
required: true,
22
unique: true,
23
max: ConstantNumber.EMAIL_MAX_LENGTH,
24
},
25
password: {
26
type: String,
27
required: true,
28
min: ConstantNumber.PASSWORD_MIN_LENGTH,
29
},
30
phone: {
31
type: String,
32
required: true,
33
unique: true,
34
min: ConstantNumber.PHONE_MIN_LENGTH,
35
max: ConstantNumber.PHONE_MAX_LENGTH,
36
},
37
address: {
38
type: String,
39
required: true,
40
min: ConstantNumber.ADDRESS_MIN_LENGTH,
41
max: ConstantNumber.ADDRESS_MAX_LENGTH,
42
},
43
isAdmin: {
44
type: Boolean,
45
default: true,
46
},
47
},
48
{
49
versionKey: false,
50
timestamps: true,
51
},
52
);
53
54
export default UserSchema;

Authentication Models

src/models/user.model.js
1
import mongoose from 'mongoose';
2
import UserSchema from '../schemas/user.schema';
3
4
import ConstantModel from '../constants/model.constant';
5
6
const UserModel = mongoose.model(ConstantModel.USER_MODEL, UserSchema);
7
8
export default UserModel;

Authentication Repositories

src/repositories/user.repository.js
1
import User from '../models/user.model';
2
3
export const findAll = async () => {
4
const users = await User.find({}).select('-password');
5
return users;
6
};
7
8
export const findById = async (id) => {
9
const user = await User.findById(id).select('-password');
10
return user;
11
};
12
13
export const findByIdWithPassword = async (id) => {
14
const user = await User.findById(id);
15
return user;
16
};
17
18
export const findByUser = async (username) => {
19
const user = await User.findOne({username}).select('-password');
20
return user;
21
};
22
23
export const findByEmail = async (email) => {
24
const user = await User.findOne({email}).select('-password');
25
return user;
26
};
27
28
export const findByEmailWithPassword = async (email) => {
29
const user = await User.findOne({email});
30
return user;
31
};
32
33
export const findByPhone = async (phone) => {
34
const user = await User.findOne({phone}).select('-password');
35
return user;
36
};
37
38
export const createUser = async (user) => {
39
const newUser = new User({
40
username: user.username,
41
name: user.name,
42
email: user.email,
43
password: user.password,
44
phone: user.phone,
45
address: user.address,
46
isAdmin: user.isAdmin,
47
});
48
const savedUser = await newUser.save();
49
return savedUser;
50
};
51
52
export const updateUsername = async (id, username) => {
53
const user = await User.findByIdAndUpdate(id, {username}, {new: true}).select(
54
'-password',
55
);
56
return user;
57
};
58
59
export const updateName = async (id, name) => {
60
const user = await User.findByIdAndUpdate(id, {name}, {new: true}).select(
61
'-password',
62
);
63
return user;
64
};
65
66
export const updateEmail = async (id, email) => {
67
const user = await User.findByIdAndUpdate(id, {email}, {new: true}).select(
68
'-password',
69
);
70
return user;
71
};
72
73
export const updatePassword = async (id, password) => {
74
const user = await User.findByIdAndUpdate(id, {password}, {new: true}).select(
75
'-password',
76
);
77
return user;
78
};
79
80
export const updatePhone = async (id, phone) => {
81
const user = await User.findByIdAndUpdate(id, {phone}, {new: true}).select(
82
'-password',
83
);
84
return user;
85
};
86
87
export const updateAddress = async (id, address) => {
88
const user = await User.findByIdAndUpdate(id, {address}, {new: true}).select(
89
'-password',
90
);
91
return user;
92
};
93
94
export const deleteUser = async (id) => {
95
const user = await User.findByIdAndDelete(id);
96
return user;
97
};
98
99
export const getUsersStats = async (lastYear) => {
100
const users = await User.aggregate([
101
{$match: {createdAt: {$gte: lastYear}}},
102
{
103
$project: {
104
month: {$month: '$createdAt'},
105
},
106
},
107
{
108
$group: {
109
_id: '$month',
110
total: {$sum: 1},
111
},
112
},
113
]);
114
return users;
115
};
116
117
export default {
118
findAll,
119
findById,
120
findByIdWithPassword,
121
findByUser,
122
findByEmail,
123
findByEmailWithPassword,
124
findByPhone,
125
createUser,
126
updateUsername,
127
updateName,
128
updateEmail,
129
updatePassword,
130
updatePhone,
131
updateAddress,
132
deleteUser,
133
getUsersStats,
134
};

Authentication Services

src/services/auth.service.js
1
import UserRepository from '../repositories/user.repository';
2
import UserSecurity from '../security/user.security';
3
import UserValidation from '../validations/user.validation';
4
5
export const validateUsername = (username) => {
6
return UserValidation.validateUsername(username);
7
};
8
9
export const validateName = (name) => {
10
return UserValidation.validateName(name);
11
};
12
13
export const validateEmail = (email) => {
14
return UserValidation.validateEmail(email);
15
};
16
17
export const validatePassword = (password) => {
18
return UserValidation.validatePassword(password);
19
};
20
21
export const comparePassword = (password, encryptedPassword) => {
22
return UserSecurity.comparePassword(password, encryptedPassword);
23
};
24
25
export const validatePhone = (phone) => {
26
return UserValidation.validatePhone(phone);
27
};
28
29
export const validateAddress = (address) => {
30
return UserValidation.validateAddress(address);
31
};
32
33
export const findByUser = async (username) => {
34
const user = await UserRepository.findByUser(username);
35
return user;
36
};
37
38
export const findByEmail = async (email) => {
39
const user = await UserRepository.findByEmailWithPassword(email);
40
return user;
41
};
42
43
export const findByPhone = async (phone) => {
44
const user = await UserRepository.findByPhone(phone);
45
return user;
46
};
47
48
export const createUser = async (user) => {
49
const encryptedPassword = UserSecurity.encryptedPassword(user.password);
50
const newUser = {
51
username: user.username,
52
name: user.name,
53
email: user.email,
54
password: encryptedPassword,
55
phone: user.phone,
56
address: user.address,
57
isAdmin: user.isAdmin,
58
};
59
const savedUser = await UserRepository.createUser(newUser);
60
return savedUser;
61
};
62
63
export const generateAccessToken = async (user) => {
64
return `Bearer ${UserSecurity.generateAccessToken(user.id, user.isAdmin)}`;
65
};
66
67
export default {
68
validateUsername,
69
validateName,
70
validateEmail,
71
validatePassword,
72
comparePassword,
73
validatePhone,
74
validateAddress,
75
findByUser,
76
findByEmail,
77
findByPhone,
78
createUser,
79
generateAccessToken,
80
};
src/services/user.service.js
1
import UserRepository from '../repositories/user.repository';
2
import UserSecurity from '../security/user.security';
3
import UserValidation from '../validations/user.validation';
4
5
export const validateUsername = (username) => {
6
return UserValidation.validateUsername(username);
7
};
8
9
export const validateName = (name) => {
10
return UserValidation.validateName(name);
11
};
12
13
export const validateEmail = (email) => {
14
return UserValidation.validateEmail(email);
15
};
16
17
export const validatePassword = (name) => {
18
return UserValidation.validatePassword(name);
19
};
20
21
export const comparePassword = (password, encryptedPassword) => {
22
return UserSecurity.comparePassword(password, encryptedPassword);
23
};
24
25
export const validatePhone = (phone) => {
26
return UserValidation.validatePhone(phone);
27
};
28
29
export const validateAddress = (address) => {
30
return UserValidation.validateAddress(address);
31
};
32
33
export const findAll = async () => {
34
const users = await UserRepository.findAll();
35
return users;
36
};
37
38
export const findById = async (id) => {
39
const user = await UserRepository.findByIdWithPassword(id);
40
return user;
41
};
42
43
export const findByIdWithOutPassword = async (id) => {
44
const user = await UserRepository.findById(id);
45
return user;
46
};
47
48
export const findByEmail = async (email) => {
49
const user = await UserRepository.findByEmail(email);
50
return user;
51
};
52
53
export const findByPhone = async (phone) => {
54
const user = await UserRepository.findByPhone(phone);
55
return user;
56
};
57
58
export const findByUser = async (username) => {
59
const user = await UserRepository.findByUser(username);
60
return user;
61
};
62
63
export const updateUsername = async (id, username) => {
64
const user = await UserRepository.updateUsername(id, username);
65
return user;
66
};
67
68
export const updateName = async (id, name) => {
69
const user = await UserRepository.updateName(id, name);
70
return user;
71
};
72
73
export const updateEmail = async (id, email) => {
74
const user = await UserRepository.updateEmail(id, email);
75
return user;
76
};
77
78
export const updatePassword = async (id, password) => {
79
const encryptedPassword = UserSecurity.encryptedPassword(password);
80
const user = await UserRepository.updatePassword(id, encryptedPassword);
81
return user;
82
};
83
84
export const updatePhone = async (id, phone) => {
85
const user = await UserRepository.updatePhone(id, phone);
86
return user;
87
};
88
89
export const updateAddress = async (id, address) => {
90
const user = await UserRepository.updateAddress(id, address);
91
return user;
92
};
93
94
export const deleteUser = async (id) => {
95
const user = await UserRepository.deleteUser(id);
96
return user;
97
};
98
99
export const getUsersStats = async () => {
100
const users = await UserRepository.getUsersStats();
101
return users;
102
};
103
104
export default {
105
validateUsername,
106
validateName,
107
validateEmail,
108
validatePassword,
109
comparePassword,
110
validatePhone,
111
validateAddress,
112
findAll,
113
findById,
114
findByIdWithOutPassword,
115
findByEmail,
116
findByPhone,
117
findByUser,
118
updateUsername,
119
updateName,
120
updateEmail,
121
updatePassword,
122
updatePhone,
123
updateAddress,
124
deleteUser,
125
getUsersStats,
126
};

Authentication Controllers

auth.controller.js

1
import ConstantMessage from '../constants/message.constant';
2
import AuthServices from '../services/auth.service';
3
4
// http constant
5
import ConstantHttpCode from '../constants/http.code.constant';
6
import ConstantHttpReason from '../constants/http.reason.constant';
7
8
// logger
9
import logger from '../utils/logger.util';
10
11
export const register = async (req, res, next) => {
12
try {
13
const {username, name, email, password, phone, address} = req.body;
14
15
const usernameValidated = AuthServices.validateUsername(username);
16
if (!usernameValidated) {
17
return res.status(ConstantHttpCode.BAD_REQUEST).json({
18
status: {
19
code: ConstantHttpCode.BAD_REQUEST,
20
msg: ConstantHttpReason.BAD_REQUEST,
21
},
22
msg: ConstantMessage.USERNAME_NOT_VALID,
23
});
24
}
25
logger.info(`username ${username} is valid`);
26
27
const nameValidated = AuthServices.validateName(name);
28
if (!nameValidated) {
29
return res.status(ConstantHttpCode.BAD_REQUEST).json({
30
status: {
31
code: ConstantHttpCode.BAD_REQUEST,
32
msg: ConstantHttpReason.BAD_REQUEST,
33
},
34
msg: ConstantMessage.NAME_NOT_VALID,
35
});
36
}
37
logger.info(`name ${name} is valid`);
38
39
const emailValidated = AuthServices.validateEmail(email);
40
if (!emailValidated) {
41
return res.status(ConstantHttpCode.BAD_REQUEST).json({
42
status: {
43
code: ConstantHttpCode.BAD_REQUEST,
44
msg: ConstantHttpReason.BAD_REQUEST,
45
},
46
msg: ConstantMessage.EMAIL_NOT_VALID,
47
});
48
}
49
logger.info(`email ${email} is valid`);
50
51
const passwordValidated = AuthServices.validatePassword(password);
52
if (!passwordValidated) {
53
return res.status(ConstantHttpCode.BAD_REQUEST).json({
54
status: {
55
code: ConstantHttpCode.BAD_REQUEST,
56
msg: ConstantHttpReason.BAD_REQUEST,
57
},
58
msg: ConstantMessage.PASSWORD_NOT_VALID,
59
});
60
}
61
62
const phoneValidated = AuthServices.validatePhone(phone);
63
if (!phoneValidated) {
64
return res.status(ConstantHttpCode.BAD_REQUEST).json({
65
status: {
66
code: ConstantHttpCode.BAD_REQUEST,
67
msg: ConstantHttpReason.BAD_REQUEST,
68
},
69
msg: ConstantMessage.PHONE_NOT_VALID,
70
});
71
}
72
73
const addressValidated = AuthServices.validateAddress(address);
74
if (!addressValidated) {
75
return res.status(ConstantHttpCode.BAD_REQUEST).json({
76
status: {
77
code: ConstantHttpCode.BAD_REQUEST,
78
msg: ConstantHttpReason.BAD_REQUEST,
79
},
80
msg: ConstantMessage.ADDRESS_NOT_VALID,
81
});
82
}
83
84
const usernameCheck = await AuthServices.findByUser(username);
85
if (usernameCheck) {
86
return res.status(ConstantHttpCode.BAD_REQUEST).json({
87
status: {
88
code: ConstantHttpCode.BAD_REQUEST,
89
msg: ConstantHttpReason.BAD_REQUEST,
90
},
91
msg: ConstantMessage.USERNAME_EXIST,
92
});
93
}
94
95
const emailCheck = await AuthServices.findByEmail(email);
96
if (emailCheck) {
97
return res.status(ConstantHttpCode.BAD_REQUEST).json({
98
status: {
99
code: ConstantHttpCode.BAD_REQUEST,
100
msg: ConstantHttpReason.BAD_REQUEST,
101
},
102
msg: ConstantMessage.EMAIL_EXIST,
103
});
104
}
105
106
const phoneCheck = await AuthServices.findByPhone(phone);
107
if (phoneCheck) {
108
return res.status(ConstantHttpCode.BAD_REQUEST).json({
109
status: {
110
code: ConstantHttpCode.BAD_REQUEST,
111
msg: ConstantHttpReason.BAD_REQUEST,
112
},
113
msg: ConstantMessage.PHONE_EXIST,
114
});
115
}
116
117
const newUserData = {
118
username,
119
name,
120
email,
121
password,
122
phone,
123
address,
124
};
125
126
const user = await AuthServices.createUser(newUserData);
127
if (!user) {
128
return res.status(ConstantHttpCode.BAD_REQUEST).json({
129
status: {
130
code: ConstantHttpCode.BAD_REQUEST,
131
msg: ConstantHttpReason.BAD_REQUEST,
132
},
133
msg: ConstantMessage.USER_NOT_CREATE,
134
});
135
}
136
137
const newUser = {...user}._doc;
138
139
logger.info({newUserpassword: newUser.password});
140
141
delete newUser.password;
142
143
logger.info({newUserpassword: newUser.password});
144
145
return res.status(ConstantHttpCode.CREATED).json({
146
status: {
147
code: ConstantHttpCode.CREATED,
148
msg: ConstantHttpReason.CREATED,
149
},
150
msg: ConstantMessage.USER_CREATE_SUCCESS,
151
data: user,
152
});
153
} catch (err) {
154
return next(err);
155
}
156
};
157
158
export const login = async (req, res, next) => {
159
try {
160
const {email, password} = req.body;
161
162
const emailValidated = AuthServices.validateEmail(email);
163
if (!emailValidated) {
164
return res.status(ConstantHttpCode.BAD_REQUEST).json({
165
status: {
166
code: ConstantHttpCode.BAD_REQUEST,
167
msg: ConstantHttpReason.BAD_REQUEST,
168
},
169
msg: ConstantMessage.EMAIL_NOT_VALID,
170
});
171
}
172
173
const passwordValidated = AuthServices.validatePassword(password);
174
if (!passwordValidated) {
175
return res.status(ConstantHttpCode.BAD_REQUEST).json({
176
status: {
177
code: ConstantHttpCode.BAD_REQUEST,
178
msg: ConstantHttpReason.BAD_REQUEST,
179
},
180
msg: ConstantMessage.PASSWORD_NOT_VALID,
181
});
182
}
183
184
const user = await AuthServices.findByEmail(email);
185
if (!user) {
186
return res.status(ConstantHttpCode.BAD_REQUEST).json({
187
status: {
188
code: ConstantHttpCode.BAD_REQUEST,
189
msg: ConstantHttpReason.BAD_REQUEST,
190
},
191
msg: ConstantMessage.USER_NOT_FOUND,
192
});
193
}
194
195
const isMatch = AuthServices.comparePassword(password, user.password);
196
if (!isMatch) {
197
return res.status(ConstantHttpCode.BAD_REQUEST).json({
198
status: {
199
code: ConstantHttpCode.BAD_REQUEST,
200
msg: ConstantHttpReason.BAD_REQUEST,
201
},
202
msg: ConstantMessage.PASSWORD_NOT_MATCH,
203
});
204
}
205
206
const accessToken = await AuthServices.generateAccessToken(user);
207
logger.info(`accessToken: ${accessToken}`);
208
209
const newUser = {...user}._doc;
210
211
logger.info({newUserpassword: newUser.password});
212
213
delete newUser.password;
214
215
logger.info({newUserpassword: newUser.password});
216
217
return res.status(ConstantHttpCode.OK).json({
218
status: {
219
code: ConstantHttpCode.OK,
220
msg: ConstantHttpReason.OK,
221
},
222
msg: ConstantMessage.LOGIN_SUCCESS,
223
data: {
224
user,
225
accessToken,
226
},
227
});
228
} catch (err) {
229
return next(err);
230
}
231
};
232
233
export default {
234
register,
235
login,
236
};

user.controller.js

1
import ConstantMessage from '../constants/message.constant';
2
import UserService from '../services/user.service';
3
4
// http constant
5
import ConstantHttpCode from '../constants/http.code.constant';
6
import ConstantHttpReason from '../constants/http.reason.constant';
7
8
// logger
9
import logger from '../utils/logger.util';
10
11
export const updateUsername = async (req, res, next) => {
12
try {
13
const {username, password} = req.body;
14
const {id} = req.params;
15
16
const user = await UserService.findById(id);
17
if (!user) {
18
return res.status(ConstantHttpCode.NOT_FOUND).json({
19
status: {
20
code: ConstantHttpCode.NOT_FOUND,
21
msg: ConstantHttpReason.NOT_FOUND,
22
},
23
msg: ConstantMessage.USER_NOT_FOUND,
24
});
25
}
26
logger.info(`user ${user.username} found`);
27
28
const usernameValidated = UserService.validateUsername(username);
29
if (!usernameValidated) {
30
return res.status(ConstantHttpCode.BAD_REQUEST).json({
31
status: {
32
code: ConstantHttpCode.BAD_REQUEST,
33
msg: ConstantHttpReason.BAD_REQUEST,
34
},
35
msg: ConstantMessage.USERNAME_NOT_VALID,
36
});
37
}
38
logger.info(`username ${username} is valid`);
39
40
const passwordValidated = UserService.validatePassword(password);
41
if (!passwordValidated) {
42
return res.status(ConstantHttpCode.BAD_REQUEST).json({
43
status: {
44
code: ConstantHttpCode.BAD_REQUEST,
45
msg: ConstantHttpReason.BAD_REQUEST,
46
},
47
msg: ConstantMessage.PASSWORD_NOT_VALID,
48
});
49
}
50
logger.info(`password ${password} is valid`);
51
52
const isMatch = UserService.comparePassword(password, user.password);
53
if (!isMatch) {
54
return res.status(ConstantHttpCode.BAD_REQUEST).json({
55
status: {
56
code: ConstantHttpCode.BAD_REQUEST,
57
msg: ConstantHttpReason.BAD_REQUEST,
58
},
59
msg: ConstantMessage.PASSWORD_NOT_MATCH,
60
});
61
}
62
63
const usernameCheck = await UserService.findByUser(username);
64
if (usernameCheck) {
65
return res.status(ConstantHttpCode.BAD_REQUEST).json({
66
status: {
67
code: ConstantHttpCode.BAD_REQUEST,
68
msg: ConstantHttpReason.BAD_REQUEST,
69
},
70
msg: ConstantMessage.USERNAME_EXIST,
71
});
72
}
73
74
if (user.username === username) {
75
return res.status(ConstantHttpCode.BAD_REQUEST).json({
76
status: {
77
code: ConstantHttpCode.BAD_REQUEST,
78
msg: ConstantHttpReason.BAD_REQUEST,
79
},
80
msg: ConstantMessage.USERNAME_NOT_CHANGE,
81
});
82
}
83
84
const updatedUser = await UserService.updateUsername(id, username);
85
if (!updatedUser) {
86
return res.status(ConstantHttpCode.BAD_REQUEST).json({
87
status: {
88
code: ConstantHttpCode.BAD_REQUEST,
89
msg: ConstantHttpReason.BAD_REQUEST,
90
},
91
msg: ConstantMessage.USERNAME_NOT_CHANGE,
92
});
93
}
94
logger.info(`user ${user.username} updated`);
95
96
return res.status(ConstantHttpCode.OK).json({
97
status: {
98
code: ConstantHttpCode.OK,
99
msg: ConstantHttpReason.OK,
100
},
101
msg: ConstantMessage.USERNAME_CHANGE_SUCCESS,
102
data: {
103
user: updatedUser,
104
},
105
});
106
} catch (err) {
107
return next(err);
108
}
109
};
110
111
export const updateName = async (req, res, next) => {
112
try {
113
const {name, password} = req.body;
114
const {id} = req.params;
115
116
const user = await UserService.findById(id);
117
if (!user) {
118
return res.status(ConstantHttpCode.NOT_FOUND).json({
119
status: {
120
code: ConstantHttpCode.NOT_FOUND,
121
msg: ConstantHttpReason.NOT_FOUND,
122
},
123
msg: ConstantMessage.USER_NOT_FOUND,
124
});
125
}
126
logger.info(`user ${user.username} found`);
127
128
const nameValidated = UserService.validateName(name);
129
if (!nameValidated) {
130
return res.status(ConstantHttpCode.BAD_REQUEST).json({
131
status: {
132
code: ConstantHttpCode.BAD_REQUEST,
133
msg: ConstantHttpReason.BAD_REQUEST,
134
},
135
msg: ConstantMessage.NAME_NOT_VALID,
136
});
137
}
138
139
const passwordValidated = UserService.validatePassword(password);
140
if (!passwordValidated) {
141
return res.status(ConstantHttpCode.BAD_REQUEST).json({
142
status: {
143
code: ConstantHttpCode.BAD_REQUEST,
144
msg: ConstantHttpReason.BAD_REQUEST,
145
},
146
msg: ConstantMessage.PASSWORD_NOT_VALID,
147
});
148
}
149
150
const isMatch = UserService.comparePassword(password, user.password);
151
if (!isMatch) {
152
return res.status(ConstantHttpCode.BAD_REQUEST).json({
153
status: {
154
code: ConstantHttpCode.BAD_REQUEST,
155
msg: ConstantHttpReason.BAD_REQUEST,
156
},
157
msg: ConstantMessage.PASSWORD_NOT_MATCH,
158
});
159
}
160
logger.info(`password ${password} is valid`);
161
162
if (user.name === name) {
163
return res.status(ConstantHttpCode.BAD_REQUEST).json({
164
status: {
165
code: ConstantHttpCode.BAD_REQUEST,
166
msg: ConstantHttpReason.BAD_REQUEST,
167
},
168
msg: ConstantMessage.NAME_NOT_CHANGE,
169
});
170
}
171
logger.info(`name ${name} is valid`);
172
173
const updatedUser = await UserService.updateName(id, name);
174
if (!updatedUser) {
175
return res.status(ConstantHttpCode.BAD_REQUEST).json({
176
status: {
177
code: ConstantHttpCode.BAD_REQUEST,
178
msg: ConstantHttpReason.BAD_REQUEST,
179
},
180
msg: ConstantMessage.NAME_NOT_CHANGE,
181
});
182
}
183
logger.info(`user ${user.username} updated`);
184
185
return res.status(ConstantHttpCode.OK).json({
186
status: {
187
code: ConstantHttpCode.OK,
188
msg: ConstantHttpReason.OK,
189
},
190
msg: ConstantMessage.NAME_CHANGE_SUCCESS,
191
data: {
192
user: updatedUser,
193
},
194
});
195
} catch (err) {
196
return next(err);
197
}
198
};
199
200
export const updateEmail = async (req, res, next) => {
201
try {
202
const {email, password} = req.body;
203
const {id} = req.params;
204
205
const user = await UserService.findById(id);
206
if (!user) {
207
return res.status(ConstantHttpCode.NOT_FOUND).json({
208
status: {
209
code: ConstantHttpCode.NOT_FOUND,
210
msg: ConstantHttpReason.NOT_FOUND,
211
},
212
msg: ConstantMessage.USER_NOT_FOUND,
213
});
214
}
215
logger.info(`user ${user.username} found`);
216
217
const emailValidated = UserService.validateEmail(email);
218
if (!emailValidated) {
219
return res.status(ConstantHttpCode.BAD_REQUEST).json({
220
status: {
221
code: ConstantHttpCode.BAD_REQUEST,
222
msg: ConstantHttpReason.BAD_REQUEST,
223
},
224
msg: ConstantMessage.EMAIL_NOT_VALID,
225
});
226
}
227
logger.info(`email ${email} is valid`);
228
229
const passwordValidated = UserService.validatePassword(password);
230
if (!passwordValidated) {
231
return res.status(ConstantHttpCode.BAD_REQUEST).json({
232
status: {
233
code: ConstantHttpCode.BAD_REQUEST,
234
msg: ConstantHttpReason.BAD_REQUEST,
235
},
236
msg: ConstantMessage.PASSWORD_NOT_VALID,
237
});
238
}
239
logger.info(`password ${password} is valid`);
240
241
const isMatch = UserService.comparePassword(password, user.password);
242
if (!isMatch) {
243
return res.status(ConstantHttpCode.BAD_REQUEST).json({
244
status: {
245
code: ConstantHttpCode.BAD_REQUEST,
246
msg: ConstantHttpReason.BAD_REQUEST,
247
},
248
msg: ConstantMessage.PASSWORD_NOT_MATCH,
249
});
250
}
251
logger.info(`password ${password} is valid`);
252
253
if (user.email === email) {
254
return res.status(ConstantHttpCode.BAD_REQUEST).json({
255
status: {
256
code: ConstantHttpCode.BAD_REQUEST,
257
msg: ConstantHttpReason.BAD_REQUEST,
258
},
259
msg: ConstantMessage.EMAIL_NOT_CHANGE,
260
});
261
}
262
logger.info(`email ${email} is valid`);
263
264
const emailCheck = await UserService.findByEmail(email);
265
if (emailCheck) {
266
return res.status(ConstantHttpCode.BAD_REQUEST).json({
267
status: {
268
code: ConstantHttpCode.BAD_REQUEST,
269
msg: ConstantHttpReason.BAD_REQUEST,
270
},
271
msg: ConstantMessage.EMAIL_EXIST,
272
});
273
}
274
275
const updatedUser = await UserService.updateEmail(id, email);
276
if (!updatedUser) {
277
return res.status(ConstantHttpCode.BAD_REQUEST).json({
278
status: {
279
code: ConstantHttpCode.BAD_REQUEST,
280
msg: ConstantHttpReason.BAD_REQUEST,
281
},
282
msg: ConstantMessage.EMAIL_NOT_CHANGE,
283
});
284
}
285
logger.info(`user ${user.username} updated`);
286
287
return res.status(ConstantHttpCode.OK).json({
288
status: {
289
code: ConstantHttpCode.OK,
290
msg: ConstantHttpReason.OK,
291
},
292
msg: ConstantMessage.EMAIL_CHANGE_SUCCESS,
293
data: {
294
user: updatedUser,
295
},
296
});
297
} catch (err) {
298
return next(err);
299
}
300
};
301
302
export const updatePassword = async (req, res, next) => {
303
try {
304
const {oldPassword, newPassword, confirmPassword} = req.body;
305
const {id} = req.params;
306
307
if (newPassword !== confirmPassword) {
308
return res.status(ConstantHttpCode.BAD_REQUEST).json({
309
status: {
310
code: ConstantHttpCode.BAD_REQUEST,
311
msg: ConstantHttpReason.BAD_REQUEST,
312
},
313
msg: ConstantMessage.PASSWORD_NOT_MATCH,
314
});
315
}
316
317
const user = await UserService.findById(id);
318
if (!user) {
319
return res.status(ConstantHttpCode.NOT_FOUND).json({
320
status: {
321
code: ConstantHttpCode.NOT_FOUND,
322
msg: ConstantHttpReason.NOT_FOUND,
323
},
324
msg: ConstantMessage.USER_NOT_FOUND,
325
});
326
}
327
logger.info(`user ${user.username} found`);
328
329
const oldPasswordValidated = UserService.validatePassword(oldPassword);
330
if (!oldPasswordValidated) {
331
return res.status(ConstantHttpCode.BAD_REQUEST).json({
332
status: {
333
code: ConstantHttpCode.BAD_REQUEST,
334
msg: ConstantHttpReason.BAD_REQUEST,
335
},
336
msg: ConstantMessage.PASSWORD_NOT_VALID,
337
});
338
}
339
logger.info(`password ${oldPassword} is valid`);
340
341
const newPasswordValidated = UserService.validatePassword(newPassword);
342
if (!newPasswordValidated) {
343
return res.status(ConstantHttpCode.BAD_REQUEST).json({
344
status: {
345
code: ConstantHttpCode.BAD_REQUEST,
346
msg: ConstantHttpReason.BAD_REQUEST,
347
},
348
msg: ConstantMessage.PASSWORD_NOT_VALID,
349
});
350
}
351
logger.info(`password ${newPassword} is valid`);
352
353
const confirmPasswordValidated =
354
UserService.validatePassword(confirmPassword);
355
if (!confirmPasswordValidated) {
356
return res.status(ConstantHttpCode.BAD_REQUEST).json({
357
status: {
358
code: ConstantHttpCode.BAD_REQUEST,
359
msg: ConstantHttpReason.BAD_REQUEST,
360
},
361
msg: ConstantMessage.PASSWORD_NOT_VALID,
362
});
363
}
364
logger.info(`password ${confirmPassword} is valid`);
365
366
if (oldPassword === newPassword) {
367
return res.status(ConstantHttpCode.BAD_REQUEST).json({
368
status: {
369
code: ConstantHttpCode.BAD_REQUEST,
370
msg: ConstantHttpReason.BAD_REQUEST,
371
},
372
msg: ConstantMessage.PASSWORD_NOT_CHANGE,
373
});
374
}
375
376
const isMatch = UserService.comparePassword(oldPassword, user.password);
377
if (!isMatch) {
378
return res.status(ConstantHttpCode.BAD_REQUEST).json({
379
status: {
380
code: ConstantHttpCode.BAD_REQUEST,
381
msg: ConstantHttpReason.BAD_REQUEST,
382
},
383
msg: ConstantMessage.PASSWORD_NOT_MATCH,
384
});
385
}
386
387
const updatedUser = await UserService.updatePassword(id, newPassword);
388
if (!updatedUser) {
389
return res.status(ConstantHttpCode.BAD_REQUEST).json({
390
status: {
391
code: ConstantHttpCode.BAD_REQUEST,
392
msg: ConstantHttpReason.BAD_REQUEST,
393
},
394
msg: ConstantMessage.PASSWORD_NOT_CHANGE,
395
});
396
}
397
logger.info(`user ${user.username} updated`);
398
399
return res.status(ConstantHttpCode.OK).json({
400
status: {
401
code: ConstantHttpCode.OK,
402
msg: ConstantHttpReason.OK,
403
},
404
msg: ConstantMessage.PASSWORD_CHANGE_SUCCESS,
405
data: {
406
user: updatedUser,
407
},
408
});
409
} catch (err) {
410
return next(err);
411
}
412
};
413
414
export const updatePhone = async (req, res, next) => {
415
try {
416
const {phone, password} = req.body;
417
const {id} = req.params;
418
419
const user = await UserService.findById(id);
420
if (!user) {
421
return res.status(ConstantHttpCode.NOT_FOUND).json({
422
status: {
423
code: ConstantHttpCode.NOT_FOUND,
424
msg: ConstantHttpReason.NOT_FOUND,
425
},
426
msg: ConstantMessage.USER_NOT_FOUND,
427
});
428
}
429
logger.info(`user ${user.username} found`);
430
431
const phoneValidated = UserService.validatePhone(phone);
432
if (!phoneValidated) {
433
return res.status(ConstantHttpCode.BAD_REQUEST).json({
434
status: {
435
code: ConstantHttpCode.BAD_REQUEST,
436
msg: ConstantHttpReason.BAD_REQUEST,
437
},
438
msg: ConstantMessage.PHONE_NOT_VALID,
439
});
440
}
441
442
const passwordValidated = UserService.validatePassword(password);
443
if (!passwordValidated) {
444
return res.status(ConstantHttpCode.BAD_REQUEST).json({
445
status: {
446
code: ConstantHttpCode.BAD_REQUEST,
447
msg: ConstantHttpReason.BAD_REQUEST,
448
},
449
msg: ConstantMessage.PASSWORD_NOT_VALID,
450
});
451
}
452
logger.info(`password ${password} is valid`);
453
454
const isMatch = UserService.comparePassword(password, user.password);
455
if (!isMatch) {
456
return res.status(ConstantHttpCode.BAD_REQUEST).json({
457
status: {
458
code: ConstantHttpCode.BAD_REQUEST,
459
msg: ConstantHttpReason.BAD_REQUEST,
460
},
461
msg: ConstantMessage.PASSWORD_NOT_MATCH,
462
});
463
}
464
logger.info(`password ${password} is valid`);
465
466
if (user.phone === phone) {
467
return res.status(ConstantHttpCode.BAD_REQUEST).json({
468
status: {
469
code: ConstantHttpCode.BAD_REQUEST,
470
msg: ConstantHttpReason.BAD_REQUEST,
471
},
472
msg: ConstantMessage.PHONE_NOT_CHANGE,
473
});
474
}
475
476
const phoneCheck = await UserService.findByPhone(phone);
477
if (phoneCheck) {
478
return res.status(ConstantHttpCode.BAD_REQUEST).json({
479
status: {
480
code: ConstantHttpCode.BAD_REQUEST,
481
msg: ConstantHttpReason.BAD_REQUEST,
482
},
483
msg: ConstantMessage.PHONE_EXIST,
484
});
485
}
486
487
const updatedUser = await UserService.updatePhone(id, phone);
488
if (!updatedUser) {
489
return res.status(ConstantHttpCode.BAD_REQUEST).json({
490
status: {
491
code: ConstantHttpCode.BAD_REQUEST,
492
msg: ConstantHttpReason.BAD_REQUEST,
493
},
494
msg: ConstantMessage.PHONE_NOT_CHANGE,
495
});
496
}
497
logger.info(`user ${user.username} updated`);
498
499
return res.status(ConstantHttpCode.OK).json({
500
status: {
501
code: ConstantHttpCode.OK,
502
msg: ConstantHttpReason.OK,
503
},
504
msg: ConstantMessage.PHONE_CHANGE_SUCCESS,
505
data: {
506
user: updatedUser,
507
},
508
});
509
} catch (err) {
510
return next(err);
511
}
512
};
513
514
export const updateAddress = async (req, res, next) => {
515
try {
516
const {address, password} = req.body;
517
const {id} = req.params;
518
519
const user = await UserService.findById(id);
520
if (!user) {
521
return res.status(ConstantHttpCode.NOT_FOUND).json({
522
status: {
523
code: ConstantHttpCode.NOT_FOUND,
524
msg: ConstantHttpReason.NOT_FOUND,
525
},
526
msg: ConstantMessage.USER_NOT_FOUND,
527
});
528
}
529
logger.info(`user ${user.username} found`);
530
531
const addressValidated = UserService.validateAddress(address);
532
if (!addressValidated) {
533
return res.status(ConstantHttpCode.BAD_REQUEST).json({
534
status: {
535
code: ConstantHttpCode.BAD_REQUEST,
536
msg: ConstantHttpReason.BAD_REQUEST,
537
},
538
msg: ConstantMessage.ADDRESS_NOT_VALID,
539
});
540
}
541
542
const isMatch = UserService.comparePassword(password, user.password);
543
if (!isMatch) {
544
return res.status(ConstantHttpCode.BAD_REQUEST).json({
545
status: {
546
code: ConstantHttpCode.BAD_REQUEST,
547
msg: ConstantHttpReason.BAD_REQUEST,
548
},
549
msg: ConstantMessage.PASSWORD_NOT_MATCH,
550
});
551
}
552
logger.info(`password ${password} is valid`);
553
554
if (user.address === address) {
555
return res.status(ConstantHttpCode.BAD_REQUEST).json({
556
status: {
557
code: ConstantHttpCode.BAD_REQUEST,
558
msg: ConstantHttpReason.BAD_REQUEST,
559
},
560
msg: ConstantMessage.ADDRESS_NOT_CHANGE,
561
});
562
}
563
564
const updatedUser = await UserService.updateAddress(id, address);
565
if (!updatedUser) {
566
return res.status(ConstantHttpCode.BAD_REQUEST).json({
567
status: {
568
code: ConstantHttpCode.BAD_REQUEST,
569
msg: ConstantHttpReason.BAD_REQUEST,
570
},
571
msg: ConstantMessage.ADDRESS_NOT_CHANGE,
572
});
573
}
574
logger.info(`user ${user.username} updated`);
575
576
return res.status(ConstantHttpCode.OK).json({
577
status: {
578
code: ConstantHttpCode.OK,
579
msg: ConstantHttpReason.OK,
580
},
581
msg: ConstantMessage.ADDRESS_CHANGE_SUCCESS,
582
data: {
583
user: updatedUser,
584
},
585
});
586
} catch (err) {
587
return next(err);
588
}
589
};
590
591
export const deleteUser = async (req, res, next) => {
592
try {
593
const {id} = req.params;
594
595
const user = await UserService.findById(id);
596
if (!user) {
597
return res.status(ConstantHttpCode.NOT_FOUND).json({
598
status: {
599
code: ConstantHttpCode.NOT_FOUND,
600
msg: ConstantHttpReason.NOT_FOUND,
601
},
602
msg: ConstantMessage.USER_NOT_FOUND,
603
});
604
}
605
logger.info(`user ${user.username} found`);
606
607
const deletedUser = await UserService.deleteUser(id);
608
if (!deletedUser) {
609
return res.status(ConstantHttpCode.BAD_REQUEST).json({
610
status: {
611
code: ConstantHttpCode.BAD_REQUEST,
612
msg: ConstantHttpReason.BAD_REQUEST,
613
},
614
msg: ConstantMessage.USER_NOT_DELETE,
615
});
616
}
617
logger.info(`user ${user.username} deleted`);
618
619
return res.status(ConstantHttpCode.OK).json({
620
status: {
621
code: ConstantHttpCode.OK,
622
msg: ConstantHttpReason.OK,
623
},
624
msg: ConstantMessage.USER_DELETE_SUCCESS,
625
});
626
} catch (err) {
627
return next(err);
628
}
629
};
630
631
export const getUser = async (req, res, next) => {
632
try {
633
const {id} = req.params;
634
logger.info(`user ${id} found`);
635
636
const user = await UserService.findByIdWithOutPassword(id);
637
logger.info(`user ${user} found`);
638
if (!user) {
639
return res.status(ConstantHttpCode.NOT_FOUND).json({
640
status: {
641
code: ConstantHttpCode.NOT_FOUND,
642
msg: ConstantHttpReason.NOT_FOUND,
643
},
644
msg: ConstantMessage.USER_NOT_FOUND,
645
});
646
}
647
logger.info(`user ${user.username} found`);
648
649
return res.status(ConstantHttpCode.OK).json({
650
status: {
651
code: ConstantHttpCode.OK,
652
msg: ConstantHttpReason.OK,
653
},
654
msg: ConstantMessage.USER_FOUND,
655
data: {
656
user,
657
},
658
});
659
} catch (err) {
660
return next(err);
661
}
662
};
663
664
export const getUsers = async (req, res, next) => {
665
try {
666
const users = await UserService.findAll();
667
if (!users) {
668
return res.status(ConstantHttpCode.NOT_FOUND).json({
669
status: {
670
code: ConstantHttpCode.NOT_FOUND,
671
msg: ConstantHttpReason.NOT_FOUND,
672
},
673
msg: ConstantMessage.USER_NOT_FOUND,
674
});
675
}
676
logger.info(`users found`);
677
678
return res.status(ConstantHttpCode.OK).json({
679
status: {
680
code: ConstantHttpCode.OK,
681
msg: ConstantHttpReason.OK,
682
},
683
msg: ConstantMessage.USER_FOUND,
684
data: {
685
users,
686
},
687
});
688
} catch (err) {
689
return next(err);
690
}
691
};
692
693
export const getUsersStats = async (req, res, next) => {
694
try {
695
const usersStats = await UserService.getUsersStats();
696
if (!usersStats) {
697
return res.status(ConstantHttpCode.NOT_FOUND).json({
698
status: {
699
code: ConstantHttpCode.NOT_FOUND,
700
msg: ConstantHttpReason.NOT_FOUND,
701
},
702
msg: ConstantMessage.USER_NOT_FOUND,
703
});
704
}
705
logger.info(`users stats found`);
706
707
return res.status(ConstantHttpCode.OK).json({
708
status: {
709
code: ConstantHttpCode.OK,
710
msg: ConstantHttpReason.OK,
711
},
712
msg: ConstantMessage.USER_FOUND,
713
data: {
714
users: usersStats,
715
},
716
});
717
} catch (err) {
718
return next(err);
719
}
720
};
721
722
export default {
723
updateUsername,
724
updateName,
725
updateEmail,
726
updatePassword,
727
updatePhone,
728
updateAddress,
729
deleteUser,
730
getUser,
731
getUsers,
732
getUsersStats,
733
};

Authentication Routes

auth.router.js

1
import express from 'express';
2
3
import ConstantAPI from '../constants/api.constant';
4
import AuthController from '../controllers/auth.controller';
5
6
const router = express.Router();
7
8
router.post(ConstantAPI.AUTH_REGISTER, AuthController.register);
9
router.post(ConstantAPI.AUTH_LOGIN, AuthController.login);
10
11
export default router;

user.router.js

1
import express from 'express';
2
3
import ConstantAPI from '../constants/api.constant';
4
import UserController from '../controllers/user.controller';
5
import TokenMiddleware from '../middlewares/token.middleware';
6
7
const router = express.Router();
8
9
router.post(
10
ConstantAPI.USER_UPDATE_USERNAME,
11
TokenMiddleware.verifyTokenAndAuthorization,
12
UserController.updateUsername,
13
);
14
router.post(
15
ConstantAPI.USER_UPDATE_NAME,
16
TokenMiddleware.verifyTokenAndAuthorization,
17
UserController.updateName,
18
);
19
router.post(
20
ConstantAPI.USER_UPDATE_EMAIL,
21
TokenMiddleware.verifyTokenAndAuthorization,
22
UserController.updateEmail,
23
);
24
router.post(
25
ConstantAPI.USER_UPDATE_PASSWORD,
26
TokenMiddleware.verifyTokenAndAuthorization,
27
UserController.updatePassword,
28
);
29
router.post(
30
ConstantAPI.USER_UPDATE_PHONE,
31
TokenMiddleware.verifyTokenAndAuthorization,
32
UserController.updatePhone,
33
);
34
router.post(
35
ConstantAPI.USER_UPDATE_ADDRESS,
36
TokenMiddleware.verifyTokenAndAuthorization,
37
UserController.updateAddress,
38
);
39
router.post(
40
ConstantAPI.USER_DELETE,
41
TokenMiddleware.verifyTokenAndAuthorization,
42
UserController.deleteUser,
43
);
44
router.get(
45
ConstantAPI.USER_GET,
46
TokenMiddleware.verifyTokenAndAuthorization,
47
UserController.getUser,
48
);
49
router.get(
50
ConstantAPI.USER_GET_ALL,
51
TokenMiddleware.verifyTokenAndAdmin,
52
UserController.getUsers,
53
);
54
router.get(
55
ConstantAPI.USER_GET_ALL_STATS,
56
TokenMiddleware.verifyTokenAndAdmin,
57
UserController.getUsersStats,
58
);
59
60
export default router;

edit index.js

1
import compression from 'compression';
2
import cookieParser from 'cookie-parser';
3
import cors from 'cors';
4
import express from 'express';
5
import helmet from 'helmet';
6
7
import connectDb from './config/db.config';
8
import {DATABASE_URL} from './env/variable.env';
9
10
// http constant
11
import ConstantHttpCode from './constants/http.code.constant';
12
import ConstantHttpReason from './constants/http.reason.constant';
13
14
// api constant
15
import ConstantAPI from './constants/api.constant';
16
17
// routers
18
import AuthRouter from './routers/auth.router';
19
import UserRouter from './routers/user.router';
20
21
connectDb(DATABASE_URL);
22
23
const app = express();
24
25
// helmet
26
app.use(helmet());
27
28
app.use(express.urlencoded({extended: true}));
29
app.use(express.json());
30
app.use(compression());
31
app.use(cors());
32
app.use(cookieParser());
33
34
app.get('/', (req, res, next) => {
35
try {
36
return res.status(ConstantHttpCode.OK).json({
37
status: {
38
code: ConstantHttpCode.OK,
39
msg: ConstantHttpReason.OK,
40
},
41
API: 'Work',
42
});
43
} catch (err) {
44
return next(err);
45
}
46
});
47
48
app.use(ConstantAPI.API_AUTH, AuthRouter);
49
app.use(ConstantAPI.API_USERS, UserRouter);
50
51
export default app;

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.

Related Posts

Check out some of our other posts

Setting up JWT Authentication in Typescript with Express, MongoDB, Babel, Prettier, ESLint, and Husky: Part 2

Setting up JWT Authentication in Typescript with Express, MongoDB, Babel, Prettier, ESLint, and Husky: Part 2

Introduction Why do we even need an authentication mechanism in an application? in my opinion, it doesn't need to be explained. The phrases authentication and authorization have likely crossed yo

read more
Introduction to Spring Boot Framework

Introduction to Spring Boot Framework

Introduction For creating web apps and microservices, many developers utilize the Spring Boot framework. The fact that it is built on top of the Spring Framework and offers a number of advantages

read more
RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?

RESTful API vs. GraphQL: Which API is the Right Choice for Your Project?

TL;DR When deciding between RESTful and GraphQL APIs for a data analysis and display application, it is important to consider the advantages and disadvantages of each. RESTful APIs have been arou

read more
Decoding REST API Architecture: A Comprehensive Guide for Developers

Decoding REST API Architecture: A Comprehensive Guide for Developers

Introduction Hey there, fellow developers! Buckle up because we're about to dive into the crazy world of REST API architecture. Prepare to decode the mysterious differences between REST API and R

read more
Mastering Caching Strategies with Redis Cache: Boosting Performance in Node.js and TypeScript

Mastering Caching Strategies with Redis Cache: Boosting Performance in Node.js and TypeScript

Introduction In the ever-evolving realm of software development, the pursuit of optimizing application performance is a perpetual endeavor. Among the arsenal of strategies to attain this goal, th

read more