Introduction
Node.js is one of the top choices for building modern, fast, and reliable applications. But turning a simple Node.js app into a production-ready solution for large companies is not easy. It needs a strong structure, careful focus on performance, and the use of best practices.
In this guide, you’ll learn how big companies set up Node.js for production. We’ll cover topics like scalable infrastructure, security, automated deployments, and monitoring. Whether you’re building your first enterprise app or improving an existing one, this blog will give you the knowledge to create software that works well and grows with your needs.
A. Installation of Nodejs
To get started, you need to have Node.js installed on your system. If you haven’t installed it yet, check out my article on How to setup Node.js for a step-by-step guide.
Create an folder for Nodejs application
Step 1 :
Goto your drive or folder where you want to create an node.js application.
Step 2 :
Open folder in vscode and then open the vscode terminal and type command :
npm init
After executing the above command, it will prompt you for a “package name”. Enter the name of your application as per your choice. For example, let’s use “standard-backend” and press Enter.
Next, it will ask for the “version”, which is set to (1.0.0) by default. Leave it as it is for now and press Enter. The version number is used to maintain the application's version history.
Then, it will prompt for a “description”, which is used to provide a brief summary of your application for other developers. For example, you can write “Complete guide to Node.js production setup” and press Enter.
After that, it will ask for the “entry point”, which defaults to index.js. Leave it as it is for now and press Enter. The entry point specifies the file from which your server will start.
Next, it will ask for a “test command”. This is where you define a command to run your test files, but for now, you can skip it by pressing Enter.
Then, it will prompt for the “git repository”. Here, you can enter the URL of the Git repository where you plan to push your code. If you don’t have a repository yet, you can skip this step by pressing Enter.
After that, it will ask for “keywords”, which help others discover your package in npm search. These are not necessary for your project unless you plan to publish your package on npm, so you can leave it blank and press Enter.
Next, it will ask for the “author”. Enter the name of the owner or author of the application.
Finally, it will ask for the “license”, which defaults to (ISC). Leave it as it is for now and press Enter. Licenses are important to let others know how they can use your code and any restrictions you place on it.
At the end, it will confirm everything by asking “Is this OK? (yes)”. Simply type yes and press Enter.
As below you can see how the above points are executed.
B. Setup of Git and Github on Nodejs application
To setup Git, you can simply install the Git from browser and set the environment variable path in your system.
To setup Github, you can simply goto github.com and create an account and create an repository.
Step 1 :
In your vscode terminal type command :
git init
Step 2 :
To create a .gitignore
file in your project, you can use it to prevent pushing sensitive files or large files (e.g., node_modules
) to your repository.
Many people create the .gitignore
file manually and then write the file paths inside it. While this method works, there’s a chance of forgetting to ignore some files. To avoid this, you can use the following command to automatically generate a .gitignore
file:
npx gitignore node
After successfully creating a .gitignore
file, you can also manually add file paths that you want to ignore.
In Visual Studio Code, you can view untracked and tracked files in the Git extension menu in the sidebar. Additionally, you can verify the changes by committing and pushing the code to your repository.
C. Setup of Husky
Husky is a popular tool for managing Git hooks in Node.js projects. It allows you to define custom scripts that run at different stages of the Git lifecycle, such as pre-commit
, pre-push
, or commit-msg
. With Husky, you can ensure code quality by automating tasks like linting, running tests, or formatting code before committing or pushing changes.
Benefits of Husky:
Enforce consistent coding standards across teams.
Prevent committing bad code by running automated checks.
Seamlessly integrate with tools like
eslint
,prettier
, andlint-staged
.
To setup you have to follow the steps :
Step 1 :
Install Husky as a development dependency:
npm install husky --save-dev
Step 2 :
Husky requires Git hooks to be enabled in your repository. Run the following command to initialize Husky:
npx husky init
This creates a .husky/
directory in your project to store Git hooks.
D. Setup of Commit-lint
Commit linting helps enforce consistent commit message formats, making it easier to understand project history and automating tasks like generating changelogs or versioning. It improves collaboration, readability, and automation in team-based development.
To setup commit lint execute below command in terminal to install some packages. remember always install commit lint package as dev-dependency.
npm i @commitlint/cli @commitlint/config-conventional --save-dev
Now, you have to create an file “commit-msg” inside “husky” folder then inside that file you have to paste below script.
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"
#!/bin/sh
: This shebang ensures the script is run with a shell.. "$(dirname "$0")/_/
husky.sh
"
: This line is required to load Husky's helper functions.npx --no-install commitlint --edit "$1"
: This runs CommitLint to check the commit message. The$1
represents the file path of the commit message (passed automatically by Git), and--no-install
prevents re-installing dependencies for each execution.
when you commit the code in git then above file will check that commit message is written in right format or not. if it is not written right then it will throw an error.
Now, finally you have to set rules for commit messages. For that you have to create an file “commitlint.config.js” and add below rules in that file.
export default {
extends: ["@commitlint/cli", "@commitlint/config-conventional"],
rules: {
"type-enum": [
2, // Level: error (2), warning (1), or disabled (0)
"always",
[
"feat", // Feature addition (e.g., adding a new feature)
"fix", // Bug fix (e.g., fixing a bug)
"docs", // Documentation changes (e.g., updating README)
"style", // Code formatting (non-functional changes, like formatting)
"refactor", // Code refactoring (e.g., restructuring code without changing functionality)
"test", // Adding or updating tests
"build", // Build-related changes (e.g., modifying build scripts)
"ci", // Continuous Integration-related changes
"chore", // Miscellaneous tasks (e.g., updating dependencies)
"revert", // Reverting changes (e.g., reverting a commit)
],
],
// "subject-case": Defines the allowed cases for the commit message subject.
"subject-case": [
2,
"always",
["sentence-case", "start-case", "lower-case"], // Allowed cases
],
},
};
To test the above setup, you have to commit some changes with random message. for e.g. git commit -m “faltu commit"
when you execute this then you see an error in your terminal like below :
If you get above error means you have successfully setup commit lint.
From now you and your team have to commit message according to rules. for e.g. if you have added some feature in application then you will write message git commit -m “feat: Login process implemented”
Note : Always commit message related to rules.
E. Setup of ESLint
We set up ESLint to enforce consistent coding styles, catch syntax errors, bad coding practice and identify potential bugs in JavaScript/TypeScript code. It helps improve code quality, maintainability, and team collaboration by ensuring adherence to predefined coding standards.
If you'd like to learn more about ESLint, you can visit their official website at https://eslint.org.
In production setups, we typically use TypeScript. To enable ESLint support for TypeScript, we use the typescript-eslint utility. Follow the steps below to set it up:
Step 1 :
npm install eslint @eslint/js typescript-eslint --save-dev
Step 2 :
Create a file named “eslint.config.js" in the root directory and paste the JSON configuration below. This file defines all the rules that adhere to best practices for a standard-grade application, ensuring developers write clean and standardized code and if you want to override some recommended rules then you can write explicitly inside rules object.
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
export default tseslint.config({
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
],
rules: {
"no-console": [2, { allow: ["warn", "log", "error"] }],
"no-debugger": "warn",
quotes: ["error", "double", { allowTemplateLiterals: true }],
},
});
you can check the error in your project by executing below command in your terminal :
npx eslint .
Step 3 :
Now you have to install “ESLint” extension also in your vs-code to see the error while writing code in development.
Step 4 :
Now, you have to write an script under “scripts” object in “package.json” file which is given below :
"lint": "eslint .",
"lint:fix": "eslint . --fix",
The scripts lint
and lint:fix
are added to streamline code linting tasks in a project:
lint: eslint .
:
This script runs ESLint on all files in the current directory (denoted by.
) to identify coding style issues, potential bugs, and violations of coding standards. It is used for checking code quality without making any modifications. This script command is similar to above commandnpx eslint .
lint:fix: eslint . --fix
:
This script runs ESLint with the--fix
flag, which automatically corrects fixable issues such as formatting errors, unused variables, or other rule violations. It reduces manual effort and ensures the code conforms to the project's linting rules.
Together, these scripts help maintain consistent and error-free code while offering an easy way to automate fixes when possible.
Step 5 :
You have to install one more below package as dev-dependency :
npm i lint-staged --save-dev
We use lint-staged
to run linters like ESLint on only the files that are staged for commit in Git.In short, we use so this package so that we never push the code with errors on git and it will throw an error when we commit our changes.
Add the following script to the root object in your “package.json” file:
"lint-staged": {
"*.ts": [
"npm run lint:fix"
]
},
This configuration ensures that the lint:fix
script is executed for all staged .ts
(TypeScript) files before they are committed.
Update the pre-commit
File
Go to the “pre-commit” file inside the “husky” folder and add the following content:
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
How It Works
Now, if there are any errors in your staged files, attempting to commit changes will throw an error in the terminal. This setup helps maintain code quality and prevents pushing invalid code to the repository.
F. Setup of Prettier
Prettier is an opinionated code formatter that automatically enforces consistent code style by parsing your code and reformatting it based on its rules. It supports multiple languages (JavaScript, TypeScript, HTML, CSS, etc.) and integrates with popular editors, ensuring clean, readable, and uniform code across your project.
If you'd like to learn more about prettier, you can visit their official website at https://prettier.io/.
Follow the steps to setup prettier :
Step 1 :
Install the package :
npm install --save-dev --save-exact prettier
Step 2 :
Install prettier extension in vscode.
Step 3 :
Create an file “.prettierrc” file in your root directory. Inside this file you can write prettier defined rules. which are described on https://prettier.io/docs/en/options.html . Below i am providing basic configuration which you can also use.
{
"printWidth": 150, // Line length before wrapping (default: 150)
"bracketSameLine": true, // Put the > of a multi-line JSX element at the end of the last line instead of being alone on the next line (does not apply to self closing elements) (default: false)
"trailingComma": "es5", // Add trailing commas where valid in ES5 (objects, arrays) (default: "es5")
"tabWidth": 2, // Number of spaces per tab (default: 2)
"semi": true, // Add a semicolon at the end of statements (default: true)
"bracketSpacing": true, // Add spaces between brackets in object literals (default: true)
"endOfLine": "crlf", // Carriage Return + Line Feed characters (\r\n), common on Windows
"quoteProps": "as-needed", // Only add quotes around object properties where required (default: "as-needed")
"singleAttributePerLine": true, // Each attribute on a separate line in JSX elements (default: false)
}
Step 4 :
When we use ESLint and Prettier together then we have to bind them so they both work perfectly in sync. so, install below package :
npm install eslint-config-prettier --save-dev
Step 5 :
import “eslintconfigPrettier“ in eslint.config.js file which we create above and add in the extends array. if can’t understand then see below eslint.config.js file :
// @ts-check
import eslint from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
export default tseslint.config({
languageOptions: {
parserOptions: {
project: true,
tsconfigRootDir: import.meta.dirname,
},
},
files: ["**/*.ts"],
extends: [
eslint.configs.recommended,
...tseslint.configs.recommendedTypeChecked,
eslintConfigPrettier
],
rules: {
"no-console": [2, { allow: ["warn", "log", "error"] }],
"no-debugger": "warn",
quotes: ["error", "double", { allowTemplateLiterals: true }],
},
});
G. Setup of Typescript
First, let’s understand why we set up TypeScript in a Node.js application. When setting up a Node.js project using JavaScript, it works fine initially. However, as the project grows and new features or modules are added, it can become challenging to manage and typecast data.
JavaScript is a dynamically-typed language, which means it doesn’t enforce strict type-checking. This is where TypeScript comes into the picture. TypeScript provides strong type-checking, detects errors at compile-time, and helps reduce runtime errors in your application. On the other hand, JavaScript executes code through an interpreter, so type-related errors might only surface during runtime.
“I recommend always creating a Node.js application using TypeScript”.
To set up TypeScript, you need to run the following command. Always remember to install TypeScript as a dev-dependency because it is only required during development for type-checking and compiling your code into JavaScript. It does not need to be included in the production environment, which helps keep your production build lightweight.
npm i typescript --save-dev
After executing above command now you have to create an tsconfig.json for that file you have to run below command.
npx tsc --init
After executing above command now you will see an file tsconfig.json in your project where basic setting are enabled. such as target : “es2016”
, module : “commonjs”
, esModuleInterop: true
, forceConsistentCasingInFileNames: true
, strict: true
, skipLibCheck: true
To know about the fields why they are enabled then you can read the comments which are written in front of them.
In industry the standard key-options enabled are given below :
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
"experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
"emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
"rootDir": "./src", /* Specify the root folder within your source files. */
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
"baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */
"paths": {
"@/src/*" : ["./src/*"],
}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./build", /* Specify an output folder for all emitted files. */
"removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
"noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
"isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
"strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
"useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
"noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
"noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
Now after setup the tsconfig.json
file we have to install types
for our nodejs application also which is given below :
npm i @types/node --save-dev
“Let me tell you that some people use -D
in place of --save-dev
so it is basically short command.”
H. Setup of Nodemon
Nodemon is used in Node.js development to automatically restart the server whenever file changes are detected, saving time and effort. It eliminates the need to manually stop and restart the server, streamlining the development process for faster iteration.
Run the below command in your terminal :
npm i nodemon --save-dev
"After installing nodemon, you need to install an additional package because nodemon cannot directly run typescript files. To resolve this, run the following command:
npm i ts-node -D
This will allow nodemon to run.ts
files."
I. Setup of Folder Structure
We will now set up the folder structure of our Node.js application, following the MVC (Model-View-Controller) pattern. MVC stands for “Model, View, and Controller.” In a production-grade application, we typically create only the model and controller folders, as the view folder is primarily used for server-side UI rendering with templating engines like EJS, Pug, or Handlebars. However, since modern applications often use frontend library like React.js for UI development, we omit the view folder.
For file naming, we will adopt the dot-notation naming convention, a widely-used standard in modular structures. If you prefer a different convention, feel free to customize it. To learn more about file-naming conventions, check out my blog, "File Naming Conventions in the Software Industry."
Now, let’s dive into setting up the structure step by step!
Step 1 :
Create an folder name "src" in root directory and it will be your main folder where we kept the server files and other files such as models, controllers, services etc..
Step 2 :
Create an “config” folder inside the “src” folder, let me tell you config folder we create because here we setup our application configuration.
Step 3 :
Create an file “env.config.ts” inside the “config” folder, Inside this file we will write environment configurations and their keys.
Step 4 :
"Create a “constants” folder inside the “src” folder. Inside this folder, we will define our application constants. You can create any constant file name you wish, but it must be related to the constants written inside that file."
Step 5 :
Create an “common.ts” file inside the “constants” folder, where we write the global constant that will be used throughout our application.
Step 6 :
Create an “controllers” folder inside the “src” folder, Let me tell you why we create controller folder because the controllers folder contains the logic for handling requests and responses in your application. Controllers act as intermediaries between the client and the service layer, processing input, calling services, and returning the appropriate response.
Step 7 :
Create an “models” folder inside the “src” folder, Let me tell you why we create models folder because the models folder in Node.js stores database schemas and handles data-related operations, keeping the code organized and easy to manage.
Step 8 :
Create an “routes” folder inside the “src” folder. we create routes folder because inside this folder we write all the application routes file such as (e.g. router.ts, auth.routes.ts, subscription.routes.ts ).
Step 9 :
Create an “services” folder inside the “src” folder.
In Node.js, a services folder is used to encapsulate business logic and reusable functions, separating them from controllers. This structure improves code organization, promotes reusability, and simplifies testing and maintenance.
Step 10 :
Now we will create an folder for typescript types of our whole application and it folder name is depend on your choice you can name folder “types” or “interfaces”. Technically types
and interface
are different in terms of use-case, you can see their use case on the typescript website.
For now i create folder “interfaces” inside the “src” folder.
Step 11 :
Create an “utils” folder inside the “src” folder.
In Node.js, a utils folder is used to store helper functions and utility modules that perform common, reusable tasks across the application. It keeps the codebase clean by centralizing these functions, reducing duplication, and improving maintainability.
Step 12 :
Create an “middlewares” folder inside the “src” folder.
In Node.js, a middlewares folder is used to organize reusable middleware functions that process requests before they reach controllers. These functions handle tasks like authentication, validation, logging, and error handling, keeping the code modular and maintainable.
Step 13 :
Create an “db” folder inside the “src” folder. we create this folder because inside this folder we write the database connection logic files.
Step 14 :
Create a “validations” folder inside the “src” folder. This folder is used to organize validation files for different API routes. These files ensure that the payloads of incoming requests are properly sanitized and validated, helping maintain data integrity and security.
Step 15 :
Create a “server.ts” file inside the “src” folder. This file serves as the entry point for starting and running the application server.
Step 16 :
Create an “app.ts” file inside the “src” folder. This file is used for setting up and configuring the express application, including middleware, routes, and any necessary application-level settings.
Step 17 :
As of now, we have created all the necessary folders and files inside the “src” folder (steps 1 to 17). Now, we will create some folders and files outside the “src” folder.
First, create a “scripts” folder in the root directory. Inside this folder, we will write the “migration” script file. Let me explain why we use migrations: Suppose you have created a user model in the database with fields like “firstname” and “lastname”, and you have deployed the application to production. Users start using the application, and after six months, the requirements change, and you need to rename “firstname” and “lastname” to “username”. If you directly change the schema, it can break the application, causing issues for users and possibly leading to data inconsistencies.
This is where migration scripts come in. Migration scripts contain database queries that help update the database schema without disrupting the application. These scripts allow you to safely update the database and manage changes over time in a structured way.
Step 18 :
Create a “nginx” folder in root directory.
In a Node.js application, the “nginx” folder is created to store configuration files for the NGINX server. NGINX is commonly used as a reverse proxy server to handle incoming HTTP requests and route them to the Node.js application. It can also be used for load balancing, SSL termination, caching, and improving performance. By organizing the NGINX configuration files within this dedicated folder, you ensure that your server configurations are separated from the application code, making it easier to manage, scale, and deploy in production environments.
Step 19 :
Create a “logs” folder in root directory and in your .gitignore file ignore logs folder also. After it create an one more .gitignore file inside the logs folder and write the following line inside that :
*
!.gitignore
The meaning of above script is that *
means ignore all the files inside the logs folder and !.gitignore
means don’t ignore .gitignore file.
A “logs” folder is created in the root directory to store log files generated by the application. These log files track important information such as error messages, application performance, and request/response data, which are essential for debugging, monitoring, and maintaining the health of the application. By organizing log files in a dedicated folder, it becomes easier to manage and analyze logs, especially when troubleshooting issues or reviewing system behavior over time. Additionally, keeping logs separate from the core application code ensures better organization and scalability in production environments.
Step 20 :
Create a “public” folder in root directory.
In Node.js, the “public” folder is used to store static assets like images, stylesheets, and JavaScript files, which can be served directly by the web server. When using React.js for the frontend, placing assets in the public folder optimizes performance by allowing the server to serve these files efficiently, enabling browser caching and reducing load times. This setup ensures that static assets are quickly accessible, improving overall application speed and responsiveness.
Step 21 :
Create a “test” folder in root directory.
The “test” folder in a Node.js application is created to store test files, ensuring that the application’s functionality is thoroughly tested. This folder helps organize unit, integration, and end-to-end tests, improving code quality, facilitating debugging, and ensuring that the application works as expected during development and deployment.
Step 22 :
This is an additional step where we create a “docker” folder. If you're working in a team and want to ensure that your project configuration remains consistent across all team members' systems, this step becomes essential.
The “docker” folder in a Node.js application is created to store Docker-related files, such as Dockerfile and docker-compose.yml, which define how the application is containerized. This folder helps streamline the process of building, deploying, and scaling the application in a consistent environment, ensuring portability and simplifying the deployment process across different systems and platforms.
So, for now create a “docker” folder and further i will tell you how to deploy your application on docker-hub.
Step 23 :
Now, we will create environment files in the root directory to manage different configurations for various environments. In industry practice, we typically create different environment files for development, testing, staging, uat and production. so create all the below files which are described in points.
1.
.env.development
(Development Environment)This file is used for the development environment, where developers work on features and run the application on their local machines. It contains configurations such as database URLs, API URLs, and debugging options for local development.
- Purpose: Allows developers to configure the environment variables to run the application on their local machine.
2. .env.testing
(Testing Environment)
This file is used for the testing environment, where the QA team runs tests (manual or automated) to verify the application’s functionality. It contains the test-specific URLs and configurations, such as test databases and testing services.
- Purpose: Provides the necessary configurations to run tests, including test URLs and databases for QA teams to perform manual or automated testing.
3. .env.staging
(Staging Environment)
This file is used for the staging environment, which closely mirrors the production environment. It allows the QA team or developers to perform performance and load testing to ensure the application works under real-world conditions before being deployed to production.
- Purpose: Provides a production-like environment where performance, load, and integration testing can be performed to ensure stability before the app goes live.
4. .env.uat
(User Acceptance Testing Environment)
This file is used for the UAT (User Acceptance Testing) environment, which is typically given to business stakeholders (such as product owners or managers) or end-users who validate whether the application meets the business requirements before going live.
- Purpose: Allows business stakeholders to perform the final validation of the application before it goes to production.
5. .env.production
(Production Environment)
This file is used for the production environment, where the live application runs for real users. It contains configurations such as real API URLs, production databases, and optimized performance settings for high availability and security.
- Purpose: Provides the final live configuration used in production to ensure that the application runs efficiently and securely for end-users.
6. .env.example
(Example Environment File)
This file contains only the keys (environment variables) required for each environment, without any sensitive values. It serves as a template to guide developers on what variables they need to define in their .env
files. The actual values for these keys are shared with developers individually through secure channels.
- Purpose: Helps developers understand which environment variables are required in each environment and gives them a reference for setting up their own
.env
files.
These files allow you to define environment-specific variables, making it easier to configure the application for different stages of deployment.
At last you have to ignore .env files in .gitignore
file because inside the env file we put sensitive credentials and key so we never push them on github. so goto .gitignore file and type below command :
.env*
Step 24 :
Create a Readme.md
file in root directory.
The Readme.md file is created to provide essential information about the project, such as its purpose, setup instructions, usage guidelines, and any dependencies or configurations. It serves as documentation for developers and users, helping them understand how to get started with the project and its features.
Step 25 :
Create a “ecosystem.config.js” file in root directory.
The ecosystem.config.js file is created to define the configuration for managing and deploying the application using PM2, a process manager for Node.js. It allows you to set environment variables, specify the number of instances, configure logging, and automate deployment, ensuring consistent and reliable application management across different environments.
Step 26 :
Create a “nodemon.json” file in root directory.
The nodemon.json file is created to configure Nodemon, a tool that automatically restarts the Node.js application during development when file changes are detected. It allows you to customize settings such as which files to watch, which files to ignore, and other Nodemon options, streamlining the development workflow.
After following all the above steps folder structure will be :
├── husky # Folder for Git hooks managed by Husky (e.g., pre-commit, pre-push hooks)
├── docker # Folder where docker file managed.
├── logs # Folder where logs file of each environment will be stored.
├── nginx # Nginx configuration files (likely for reverse proxy or load balancing)
├── node_modules # Folder for installed npm packages
├── public # Public folder for static files (e.g., images, CSS, JavaScript files)
├── script # Custom scripts for migrations tasks
├── src/ # Main source code of the application
│ ├── config/ # Configuration files (e.g., for DB, environment variables)
│ │ └── env.config.ts # Configuration file to handle environment-specific variables
│ ├── constants/ # Constants used throughout the app (e.g., common values, static data)
│ │ └── common.ts # Common constants used across the app
│ ├── controllers/ # Controllers to handle API requests (business logic layer)
│ │ ├── user.controller.ts
│ │ ├── auth.controller.ts
│ │ └── subscription.controller.ts
│ ├── db/ # Database-related files
│ │ └── connection.ts # Database connection setup (e.g., for MongoDB or other databases)
│ ├── interfaces/ # TypeScript interfaces for type safety
│ │ ├── user.interface.ts # User-related interface definitions
│ │ ├── subscription.interface.ts # Subscription-related interface definitions
│ ├── middlewares/ # Middlewares like authentication, logging, etc.
│ │ └── auth.middleware.ts # Middleware to check user authentication
│ ├── models/ # Database models (e.g., MongoDB Mongoose models)
│ │ ├── user.model.ts # User model (e.g., for MongoDB)
│ │ ├── subscription.model.ts # Subscription model (e.g., for MongoDB)
│ │ └── order.model.ts # Order model (e.g., for MongoDB)
│ ├── routes/ # API route definitions
│ │ ├── router.ts # Main router file to tie all routes together
│ │ ├── auth.routes.ts # Routes related to authentication (e.g., login, register)
│ │ └── subscription.routes.ts # Routes related to subscriptions (e.g., create, update)
│ ├── services/ # Services that contain the business logic
│ │ ├── user.service.ts
│ │ ├── auth.service.ts
│ │ └── subscription.service.ts
│ ├── utils/ # Utility functions/helpers (e.g., to aid in operations or transformations)
│ │ └── helper.ts # Helper functions (e.g., formatting or common operations)
│ ├── validations/ # Validation files for validating input and request data using Joi
│ │ └── auth.validation.ts # Validation logic for authentication-related data (e.g., email, password)
│ ├── app.ts # Main app file that configures Express, middlewares, routes, etc.
│ ├── server.ts # Entry point to start the server (e.g., listening on a port)
│ ├── tests/ # Folder for testing your application
│ ├── .eslintignore # List of files/folders to ignore during ESLint checks
│ ├── .gitignore # List of files/folders to ignore when pushing to GitHub
│ ├── .prettierignore # List of files/folders to ignore when formatting code with Prettier
│ ├── .prettierrc # Prettier configuration rules to standardize code formatting
│ ├── commitlint.config.mjs # Commit linting rules for conventional commit messages
│ ├── ecosystem.config.js # PM2 configuration file for app deployment and management
│ ├── eslint.config.js # ESLint configuration rules to follow best coding practices
│ ├── nodemon.json # Nodemon configuration for auto-reloading server during development
├── .env.development # Environment variables for development environment
├── .env.testing # Environment variables for testing environment
├── .env.staging # Environment variables for staging environment
├── .env.uat # Environment variables for uat environment
├── .env.production # Environment variables for production environment
├── package-lock.json # Lock file for npm dependencies
├── package.json # Project metadata and npm dependencies and scripts
├── README.md # Project documentation with installation and usage instructions
└── tsconfig.json # TypeScript configuration file (e.g., for compiler options, module resolution)
Folder structure will be look like :
J. Setup of environment files
As we create all the required .env file now we have to configure them so that we can run the following `env file according to provided script. so follow the steps given below :
Step 1 :
Install the dotenv package :
npm i dotenv
Step 2 :
Write below code into .env.config.ts file and if want to understand the code more clearly then i will provide github repo link at last :
import dotenv from "dotenv";
import { ENV_FILE, ENV_MODE } from "../constants/constant";
const environment = process.env.NODE_ENV;
const envFilePath =
environment === ENV_MODE.DEVELOPMENT
? ENV_FILE.DEVELOPMENT_ENV
: environment === ENV_MODE.UAT
? ENV_FILE.UAT_ENV
: environment === ENV_MODE.PRODUCTION
? ENV_FILE.PRODUCTION_ENV
: "";
dotenv.config({ path: envFilePath });
const envConfig = {
PORT: process.env.PORT,
NODE_ENV: process.env.NODE_ENV,
MONGO_URL: process.env.MONGO_URL,
};
export default envConfig;
you have to paste the below code in “nodemon.json” file :
{
"ext": ".ts",
"ignore": ["build", "node_modules"]
}
Now, add the below script under “scripts” object in package.json file.
{
"start:dev": "set NODE_ENV=development&&nodemon",
"start:uat": "set NODE_ENV=uat&&nodemon",
"start:prod": "set NODE_ENV=production ./build/server.ts",
}
now, whenever you want to run specific environment then you run the above script. for e.g.
npm run start:dev // for development environment
npm run start:uat // for uat environment
npm run start:prod // for production environment
H. Setup of Global Error Handler
you just need to use below provided global error handler as middleware. i covered most of the case but if some case left then you can add.
import { Request, Response, NextFunction } from "express";
import { Error as MongooseError } from "mongoose";
import envConfig from "../config/env.config";
import { MongoError } from "mongodb";
import { ENV_MODE } from "../constants/constant";
// Global Error Handler
const globalErrorHandler = (
err: unknown, // Use the defined CustomError type
_: Request,
res: Response,
next: NextFunction
) => {
if ([ENV_MODE.UAT, ENV_MODE.PRODUCTION].includes(envConfig.NODE_ENV as "uat" | "production")) {
// Handle Mongoose Validation Errors (e.g., missing required fields)
if (err instanceof MongooseError.ValidationError) {
res.status(400).json({
success: false,
message: "Validation error, please check the input data.",
});
}
// Handle Duplicate Key Errors (e.g., unique index violation)
else if (err instanceof MongooseError.CastError) {
res.status(400).json({
success: false,
message: `Invalid data format for field: ${err.path}`,
});
} else if (err instanceof MongoError && err.code === 11000) {
// Handle duplicate id error
res.status(409).json({
success: false,
message: "Duplicate entry found.",
});
}
// General Database Error Handling
else {
res.status(500).json({
success: false,
message: "An error occurred while processing your request.",
});
}
} else {
console.error("Error Handler", err);
// In development, show full error details
res.status(400).json({
success: false,
message: "Database error occurred",
error: err instanceof Error ? err.message : err,
stack: err instanceof Error ? err.stack : null,
});
}
next(err);
};
export default globalErrorHandler;
I. Additional
In above all the steps you get to know that how we can setup the production grade nodejs application. To know code more clearly you can check my github repo where i covered lot of thing such as setup of winston logger, 404 error handler and can check the flow of route, controller and services.
Here’s a comprehensive list of top NPM libraries frequently used in Node.js applications, categorized by their functionality:
1. HTTP & Web Frameworks
Express: Minimal and flexible web framework for building APIs and web applications.
Fastify: Fast and low-overhead web framework for Node.js.
Koa: Lightweight and modular framework for building web applications.
NestJS: A framework for building scalable and maintainable server-side applications.
Hapi: A robust framework for building APIs.
2. Authentication & Authorization
Passport: Middleware for authentication using strategies like OAuth, JWT, etc.
jsonwebtoken (JWT): For creating and verifying JSON Web Tokens.
OAuth2orize: Framework for building OAuth 2.0 authorization servers.
Bcrypt.js: Library for hashing passwords.
Argon2: Another password-hashing library, considered stronger than bcrypt.
3. Database Management
Mongoose: MongoDB object modeling tool.
Sequelize: ORM for relational databases like MySQL, PostgreSQL, SQLite.
TypeORM: ORM for TypeScript and JavaScript.
Prisma: Modern ORM for relational databases.
Knex.js: SQL query builder for Node.js.
4. API Development
Axios: Promise-based HTTP client.
Node-Fetch: Lightweight HTTP client for server-side usage.
Swagger UI Express: To create interactive API documentation.
APIDoc: Auto-generate API documentation.
5. Real-Time Communication
6. Task Scheduling & Background Jobs
Bull: For handling background jobs and queues (Redis-based).
Agenda: Lightweight job scheduling.
Node-Schedule: Cron-like and flexible scheduling.
7. File Handling
Multer: Middleware for handling file uploads.
Sharp: High-performance image processing library.
Formidable: Parse form data, including file uploads.
PDFKit: Generate PDF documents programmatically.
8. Logging & Monitoring
Winston: Flexible logging library.
Morgan: HTTP request logger middleware.
Pino: Fast and efficient logging library.
Sentry: Error monitoring and performance tracking.
9. Security
Helmet: Middleware to secure Express apps by setting HTTP headers.
Cors: Middleware to enable Cross-Origin Resource Sharing.
Rate-Limit: Rate-limiting middleware to prevent abuse.
CSURF: Middleware for CSRF protection.
10. Testing
Mocha: Test framework for Node.js.
Jest: Comprehensive testing framework.
Chai: Assertion library for Node.js.
Supertest: HTTP assertions and integration testing.
Sinon: Mocking and spying for testing.
11. Utilities
Lodash: Utility library for common operations.
Moment.js: Date and time manipulation.
Day.js: Lightweight alternative to Moment.js.
UUID: Generate universally unique identifiers.
12. Environment Management
Dotenv: Load environment variables from
.env
file.Config: Manage application configuration in Node.js.
13. Data Validation & Parsing
Joi: Schema validation for data.
Yup: Validation and schema building for JavaScript objects.
Validator.js: String validation and sanitization.
14. CLI Tools
Commander: Build command-line interfaces.
Inquirer: Interactive command-line prompts.
Yargs: Create CLI tools easily.
15. Performance Optimization
Compression: Middleware for gzip compression.
Cluster: Built-in Node.js module for multi-core processing.
16. Dev Tools
Nodemon: Automatically restart your app on code changes.
PM2: Advanced process manager for production Node.js applications.
Above project source link : Link
Thanks for reading, if this blog get helpful to you then please like 👍 and share...