create-new-express-app
Version:
A CLI tool to create a TypeScript Express application with best practices
1,210 lines (943 loc) • 39.5 kB
Markdown
# Setup a Nodejs Express Application with TypeScript
## Introduction
This is a simple guide to setup a Nodejs Express application with TypeScript. This guide will help you to setup a Nodejs Express application with TypeScript, ESLint, Prettier, and Husky.
## Prerequisites
- Nodejs
- NPM
- Git
## Step 1: Create a project directory
Create a new directory for your project.
```bash
mkdir nodejs-express-typescript
cd nodejs-express-typescript
```
## Step 2: Initialize a new Nodejs project
Run the following command to initialize a new Nodejs project.
```bash
npm init -y
```
## Step 3: Install Dependencies
Install the required dependencies for the project.
```bash
npm i express dotenv cors helmet http-status-codes rimraf zod
npm i -D typescript @types/express @types/dotenv @types/helmet @types/cors
```
Dependencies:
- `express`: Fast, unopinionated, minimalist web framework for Node.js.
- `dotenv`: Loads environment variables from a `.env` file.
- `cors`: CORS is a node.js package for providing a Connect/Express middleware that can be used to enable CORS with various options.
- `helmet`: Helmet helps you secure your Express apps by setting various HTTP headers.
- `http-status-codes`: Constants enumerating the HTTP status codes.
- `rimraf`: A deep deletion module for node (like `rm -rf`).
- `zod`: TypeScript-first schema declaration and validation library.
- `typescript`: TypeScript is a language for application-scale JavaScript.
We also installed the types for the dependencies.
## Step 4: Initialize TypeScript
Run the following command to initialize TypeScript in the project.
```bash
npx tsc --init
```
## Step 5: Configure TypeScript
Update the `tsconfig.json` file with the following configuration.
```json
{
"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": "ES2021" /* 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 legacy experimental 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": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* 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. */
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
"resolveJsonModule": true /* Enable importing .json files. */,
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
// "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. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "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. */
// "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. */
// "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. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
// "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */
// "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. */
}
}
```
## Step 6: Create a source directory
Create a new directory `src` in the project directory.
```bash
mkdir src
```
## Step 7: Setup ESLint and Prettier
Run the following command to install ESLint and Prettier in the project.
```bash
npm install -D eslint eslint-config-prettier prettier
```
Run the following command to initialize ESLint in the project.
```bash
npm init @eslint/config@latest
```
Run the following command to install the prettier plugin for ESLint.
```bash
node --eval "fs.writeFileSync('.prettierignore','# Ignore artifacts: \nbuild\ncoverage\n\n# Ignore all dotfiles\n.*\n\n# Ignore lock files\npackage-lock.json\nyarn.lock\n\n# Ignore node_modules and public folders\nnode_modules\npublic\n')"
```
Add this .prettierrc file to the project root directory:
```json
{
"bracketSpacing": true,
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 80,
"tabWidth": 2
}
```
Add eslint-config-prettier to make ESLint and Prettier play nice with each other.
Updated eslint.config.mjs file:
```typescript
import globals from "globals";
import pluginJs from "@eslint/js";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier";
export default [
{ files: ["**/*.{js,mjs,cjs,ts}"] },
{ languageOptions: { globals: globals.browser } },
pluginJs.configs.recommended,
...tseslint.configs.recommended,
eslintConfigPrettier,
];
```
## Step 8: Setup Husky and Lint-Staged
Run the following command to install Husky and Lint-Staged in the project.
```bash
npm install --save-dev husky lint-staged
npx husky init
node --eval "fs.writeFileSync('.husky/pre-commit','npx lint-staged\n')"
node --eval "fs.writeFileSync('.huskyrc','{\n \"hooks\": {\n \"pre-commit\": \"npx lint-staged\"\n }\n}')"
node --eval "fs.writeFileSync('.lintstagedrc','{}\n')"
node --eval "fs.writeFileSync('.husky/install.mjs', '')"
```
Add the following configuration for lint-staged
```json
{
"*/**/*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix", "eslint"],
"*/**/*.{json,css,md}": ["prettier --write"]
}
```
Add the following configuration for husky install
```javascript
import { process } from "node";
// Skip Husky install in production and CI
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
process.exit(0);
}
const husky = (await import("husky")).default;
console.log(husky());
```
## Step 9: Setup Morgan and Winston for Logging
Run the following command to install Morgan and Winston in the project.
```bash
npm install morgan winston
```
Install the types for the dependencies.
```bash
npm install -D @types/morgan
```
Create a new directory src/middlewares in the project directory.
```bash
mkdir src/middlewares
```
Create a new file src/middlewares/morgan.middleware.ts in the project directory.
```bash
touch src/middlewares/morgan.middleware.ts
```
Add the following code to the morgan.middleware.ts file.
```typescript
import logger from "@/utils/logger";
import morgan, { type StreamOptions } from "morgan";
// Override the stream method by telling
// Morgan to use our custom logger instead of the console.log.
const stream: StreamOptions = {
// Use the http severity
write: (message) => logger.http(message),
};
// Skip all the Morgan http log if the
// application is not running in development mode.
// This method is not really needed here since
// we already told to the logger that it should print
// only warning and error messages in production.
const skip = () => {
const env = process.env.NODE_ENV || "development";
return env !== "development";
};
// Build the morgan middleware
const morganMiddleware = morgan(
// Define message format string (this is the default one).
// The message format is made from tokens, and each token is
// defined inside the Morgan library.
// You can create your custom token to show what do you want from a request.
":method :url :status :res[content-length] - :response-time ms",
// Options: in this case, I overwrote the stream and the skip logic.
// See the methods above.
{ stream, skip }
);
export default morganMiddleware;
```
Create a new directory src/utils in the project directory.
```bash
mkdir src/utils
```
Create a new file src/utils/logger.ts in the project directory.
```bash
touch src/utils/logger.ts
```
Add the following code to the logger.ts file.
```typescript
import winston from "winston";
// import * as path from "path";
// import * as fs from "fs";
// const logDir = path.join(__dirname, "../utils/logs");
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
debug: 4,
};
const level = () => {
const env = process.env.NODE_ENV || "development";
const isDevelopment = env === "development";
return isDevelopment ? "debug" : "warn";
};
const colors = {
error: "red",
warn: "yellow",
info: "green",
http: "bold cyan",
debug: "white",
};
winston.addColors(colors);
const format = winston.format.combine(
winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss:ms" }),
winston.format.colorize({ all: true }),
winston.format.printf((info) => `${info.timestamp} ${info.level}: ${info.message}`)
);
const transports = [
new winston.transports.Console(),
// new winston.transports.File({
// filename: "./src/v1/utils/logs/error.log",
// level: "error",
// }),
// new winston.transports.File({ filename: "./src/v1/utils/logs/all.log" }),
];
const logger = winston.createLogger({
level: level(),
levels,
format,
transports,
});
export default logger;
```
## Step 10: Setup Zod for Validation
Run the following command to install Zod in the project.
```bash
npm install zod
```
Create a new file src/middlewares/validation.middleware.ts in the project directory.
```bash
touch src/middlewares/validation.middleware.ts
```
Add the following code to the validation.middleware.ts file.
```typescript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { type Request, type Response, type NextFunction } from "express";
import { type z, ZodError } from "zod";
import { StatusCodes } from "http-status-codes";
export function validateData(schema: z.ZodObject<any, any>) {
return (req: Request, res: Response, next: NextFunction) => {
try {
schema.parse(req.body);
next();
} catch (error) {
if (error instanceof ZodError) {
// Constructing a more professional and verbose error message
const errorMessages = error.errors.map((issue: any) => {
const field = issue.path.join(".");
const message = issue.message;
return `${field} is ${message.toLowerCase()}.`;
});
res.status(StatusCodes.BAD_REQUEST).json({
error: error,
message: `Please correct the following errors: ${errorMessages.join(" ")}`, // Concatenating all messages with a space for readability
description: error.issues,
code: StatusCodes.BAD_REQUEST,
});
} else {
res.status(StatusCodes.BAD_REQUEST).json({
error: error,
message: "Please correct the errors",
description: error,
code: StatusCodes.BAD_REQUEST,
});
}
}
};
}
```
## Step 11: Setup Swagger for API Documentation
Run the following command to install Swagger in the project.
```bash
npm install -D swagger-jsdoc swagger-ui-express @types/swagger-jsdoc @types/swagger-ui-express
```
Create a new file src/utils/swagger.ts in the project directory.
```bash
touch src/utils/swagger.ts
```
Add the following code to the swagger.ts file.
```typescript
import swaggerJsdoc from "swagger-jsdoc";
import swaggerUi from "swagger-ui-express";
const options: swaggerJsdoc.Options = {
definition: {
openapi: "3.0.1",
info: {
title: "REST API with Express and Swagger",
version: "0.0.1",
description:
"This is a simple REST API application made with Express and documented with Swagger",
},
swagger: `http://localhost:${process.env.PORT}/api/docs/json`,
externalDocs: {
url: `http://localhost:${process.env.PORT}/api/docs/json`,
description: "OpenAPI - JSON",
},
tags: [
{
name: "Health Check",
description: "Endpoints to check the health of the Feedback-MS",
},
{
name: "Echo",
description: "Endpoints to echo back the request",
},
],
},
apis: ["**/*.ts"],
};
const specs = swaggerJsdoc(options);
export { specs, swaggerUi };
```
## Step 12: Setup Express Application
Create a new file src/index.ts in the project directory.
```bash
touch src/server.ts
```
Add the following code to the server.ts file.
```typescript
import app from "./app";
import Server from "http";
import dotenv from "dotenv";
import logger from "@/utils/logger";
dotenv.config();
const PORT = process.env.PORT || 5000;
// Initialize Application Insights
// appInsightsSetup(process.env.APPLICATIONINSIGHTS_CONNECTION_STRING || "");
const server = Server.createServer(app);
server.listen(PORT, () => {
logger.info(`App running at http://localhost:${PORT}`);
});
```
Create a new file src/app.ts in the project directory.
```bash
touch src/app.ts
```
Add the following code to the app.ts file.
```typescript
import express, { type Request, type Response, type Application } from "express";
import cors from "cors";
import helmet from "helmet";
import morganMiddleware from "@/middlewares/morgan.middleware";
import { handleErrors, notFound } from "@/middlewares";
import { specs, swaggerUi } from "@/utils/swagger";
import { StatusCodes } from "http-status-codes";
import router from "@/routers";
const app: Application = express();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morganMiddleware);
app.use(cors());
app.use(helmet());
app.use("/api/docs/json", (req: Request, res: Response) => {
res.setHeader("Content-Type", "application/json");
res.send(specs);
});
/*
@swagger
/:
get:
summary: Welcome to Feedback-MS
description: Welcome to Feedback-MS
responses:
200:
description: Welcome to Feedback-MS
content:
application/json:
schema:
type: object
properties:
message:
type: string
example: Welcome to Feedback-MS
*/
app.get("/", (req, res) => {
res.status(StatusCodes.OK).json({ message: "Welcome to Feedback-MS" });
});
app.use("/api/docs", swaggerUi.serve, swaggerUi.setup(specs, { explorer: true }));
app.use("/api", router);
app.use(handleErrors);
app.use(notFound);
export default app;
```
## Step 13: Create a router
Create a new directory src/routers in the project directory.
```bash
mkdir src/routers
```
Create a new file src/routers/index.ts in the project directory.
```bash
touch src/routers/index.ts
```
Add the following code to the index.ts file.
```typescript
import express from "express";
import { StatusCodes } from "http-status-codes";
import echoRouter from "@/routers/echo.router";
const router = express.Router();
/**
* @swagger
* /api/health:
* get:
* summary: Health check
* description: Returns a JSON response indicating that the Feedback-MS is up and running.
* tags:
* - Health Check
* responses:
* 200:
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* description: A message indicating the status of the Feedback-MS.
* example: Feedback-MS Up and running
*/
router.get("/health", (req, res) => {
res.status(StatusCodes.OK).json({ status: "UP" });
});
router.use("/echo", echoRouter);
export default router;
```
Create a new file src/routers/echo.router.ts in the project directory.
```bash
touch src/routers/echo.router.ts
```
Add the following code to the echo.router.ts file.
```typescript
import { validateData } from "@/middlewares";
import { echoSchema } from "@/schemas/echo.schema";
import { echo } from "@/controllers";
import express from "express";
const router = express.Router();
/**
* @swagger
* /echo:
* post:
* summary: Echo the message
* description: Returns the message sent in the request body.
* tags:
* - Echo
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Hello, world!"
* responses:
* 200:
* description: Successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* example: "Hello, world!"
* 400:
* description: Bad Request
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/ErrorResponseBody'
*/
router.post("/", validateData(echoSchema), echo);
export default router;
```
## Step 14: Create a controller
Create a new directory src/controllers in the project directory.
```bash
mkdir src/controllers
```
Create a new file src/controllers/echo.controller.ts in the project directory.
```bash
touch src/controllers/echo.controller.ts
```
Add the following code to the echo.controller.ts file.
```typescript
import { type Request, type Response } from "express";
import { StatusCodes } from "http-status-codes";
const echo = async (req: Request, res: Response) => {
const { message } = req.body;
res.status(StatusCodes.OK).json({ message });
};
export { echo };
```
## Step 15: Create a schema
Create a new directory src/schemas in the project directory.
```bash
mkdir src/schemas
```
Create a new file src/schemas/echo.schema.ts in the project directory.
```bash
touch src/schemas/echo.schema.ts
```
Add the following code to the echo.schema.ts file.
```typescript
import { z } from "zod";
/*
* @swagger
* components:
* schemas:
* EchoSchema:
* type: object
* properties:
* message:
* type: string
* minLength: 1
* maxLength: 255
* example: Hello, World!
* required:
* - message
* description: The message to be echoed back.
*/
export const echoSchema = z.object({
message: z.string().min(1).max(255),
});
export type EchoSchema = z.infer<typeof echoSchema>;
```
## Step 16: Create a .env file
Create a new file .env in the project directory.
```bash
touch .env
```
Add the following configuration to the .env file.
```env
PORT=5000
NODE_ENV=development
```
## Step 17: Add development scripts
Install the following development dependencies.
```bash
npm install -D nodemon ts-node-dev tsconfig-paths
```
Create a file nodemon.json in the project directory.
```bash
touch nodemon.json
```
Add the following configuration to the nodemon.json file.
```json
{
"watch": ["src", ".env"],
"ignore": ["src/**/*.spec.ts"],
"ext": "ts,js,json",
"exec": "ts-node-dev --respawn --transpile-only -r tsconfig-paths/register src/server.ts"
}
```
Add the following scripts to the package.json file.
```json
{
"scripts": {
"start": "node build/server.js",
"dev": "nodemon",
"build": "rimraf build && tsc",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"prettier": "prettier --write .",
"prettier:check": "prettier --check ."
}
}
```
## Step 17: Run the application
Run the following command to start the application.
```bash
npm run dev
```
The application will start at http://localhost:5000.
## Step 18: Add Testing to the Application
Install the following development dependencies.
```bash
npm install -D jest supertest ts-jest @types/jest @types/supertest
```
Create a new directory src/tests in the project directory.
```bash
mkdir src/tests
```
Create a new file src/tests/echo.test.ts in the project directory.
```bash
touch src/tests/echo.test.ts
```
Add the following code to the echo.test.ts file.
```typescript
import request from "supertest";
import app from "@/app";
import { StatusCodes } from "http-status-codes";
describe("Echo Controller", () => {
it("should echo the message", async () => {
const message = "Hello, World!";
const response = await request(app).post("/api/echo").send({ message });
expect(response.status).toBe(StatusCodes.OK);
expect(response.body.message).toBe(message);
});
it("should return bad request for empty message", async () => {
const response = await request(app).post("/api/echo").send({});
expect(response.status).toBe(StatusCodes.BAD_REQUEST);
});
});
```
Generate a jest configuration file.
```bash
npx ts-jest config:init
```
Update the jest configuration file.
```typescript
/**
* For a detailed explanation regarding each configuration property, visit:
* https://jestjs.io/docs/configuration
*/
import type { Config } from "jest";
const config: Config = {
// All imported modules in your tests should be mocked automatically
// automock: false,
// Stop running tests after `n` failures
// bail: 0,
// The directory where Jest should store its cached dependency information
// cacheDirectory: "C:\\Users\\nitin\\AppData\\Local\\Temp\\jest",
// Automatically clear mock calls, instances, contexts and results before every test
clearMocks: true,
// Indicates whether the coverage information should be collected while executing the test
collectCoverage: true,
// An array of glob patterns indicating a set of files for which coverage information should be collected
// collectCoverageFrom: undefined,
// The directory where Jest should output its coverage files
coverageDirectory: "coverage",
// An array of regexp pattern strings used to skip coverage collection
// coveragePathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// Indicates which provider should be used to instrument code for coverage
coverageProvider: "v8",
// A list of reporter names that Jest uses when writing coverage reports
// coverageReporters: [
// "json",
// "text",
// "lcov",
// "clover"
// ],
// An object that configures minimum threshold enforcement for coverage results
// coverageThreshold: undefined,
// A path to a custom dependency extractor
// dependencyExtractor: undefined,
// Make calling deprecated APIs throw helpful error messages
// errorOnDeprecated: false,
// The default configuration for fake timers
// fakeTimers: {
// "enableGlobally": false
// },
// Force coverage collection from ignored files using an array of glob patterns
// forceCoverageMatch: [],
// A path to a module which exports an async function that is triggered once before all test suites
// globalSetup: undefined,
// A path to a module which exports an async function that is triggered once after all test suites
// globalTeardown: undefined,
// A set of global variables that need to be available in all test environments
// globals: {},
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
// maxWorkers: "50%",
// An array of directory names to be searched recursively up from the requiring module's location
// moduleDirectories: [
// "node_modules"
// ],
// An array of file extensions your modules use
// moduleFileExtensions: [
// "js",
// "mjs",
// "cjs",
// "jsx",
// "ts",
// "tsx",
// "json",
// "node"
// ],
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
"^@/(.*)$": "<rootDir>/src/$1",
},
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
// modulePathIgnorePatterns: [],
// Activates notifications for test results
// notify: false,
// An enum that specifies notification mode. Requires { notify: true }
// notifyMode: "failure-change",
// A preset that is used as a base for Jest's configuration
// preset: undefined,
// Run tests from one or more projects
// projects: undefined,
// Use this configuration option to add custom reporters to Jest
// reporters: undefined,
// Automatically reset mock state before every test
// resetMocks: false,
// Reset the module registry before running each individual test
// resetModules: false,
// A path to a custom resolver
// resolver: undefined,
// Automatically restore mock state and implementation before every test
// restoreMocks: false,
// The root directory that Jest should scan for tests and modules within
// rootDir: undefined,
// A list of paths to directories that Jest should use to search for files in
// roots: [
// "<rootDir>"
// ],
// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
// The paths to modules that run some code to configure or set up the testing environment before each test
// setupFiles: [],
// A list of paths to modules that run some code to configure or set up the testing framework before each test
// setupFilesAfterEnv: [],
// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
// snapshotSerializers: [],
// The test environment that will be used for testing
// testEnvironment: "jest-environment-node",
// Options that will be passed to the testEnvironment
// testEnvironmentOptions: {},
// Adds a location field to test results
// testLocationInResults: false,
// The glob patterns Jest uses to detect test files
// testMatch: [
// "**/__tests__/**/*.[jt]s?(x)",
// "**/?(*.)+(spec|test).[tj]s?(x)"
// ],
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
// testPathIgnorePatterns: [
// "\\\\node_modules\\\\"
// ],
// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
// This option allows the use of a custom results processor
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
transform: {
"^.+.tsx?$": ["ts-jest", {}],
},
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: ["\\\\node_modules\\\\", "\\.pnp\\.[^\\\\]+$"],
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
// Indicates whether each individual test should be reported during the run
// verbose: undefined,
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
// watchPathIgnorePatterns: [],
// Whether to use watchman for file crawling
// watchman: true,
};
export default config;
```
Run the following command to run the tests.
```bash
npm test
```
## Step 19: Run the application in production
Install tsc-alias to resolve the aliases in the production build.
```bash
npm install -D tsc-alias
```
Update the following scripts in package.json.
```json
{
"scripts": {
"start": "node build/server.js",
"dev": "nodemon",
"build": "rimraf build && tsc --build && tsc-alias",
"lint": "eslint . --ext .ts",
"lint:fix": "eslint . --ext .ts --fix",
"prettier": "prettier --write .",
"prettier:check": "prettier --check .",
"prepare": "husky || true",
"test": "jest"
}
}
```
Run the following command to build the application.
```bash
npm run build
```
Run the following command to start the application in production.
```bash
npm start
```
## Step 20: Add Docker Support
Run the following command to initialize the Docker in the project directory.
```bash
docker init
```
Follow the steps and select the default options.
## Overall Project Structure
```bash
nodejs-express-typescript
├── .dockerignore
├── .env
├── .env.example
├── .gitignore
├── .husky
│ ├── install.mjs
│ └── pre-commit
├── .huskyrc
├── .lintstagedrc
├── .prettierignore
├── .prettierrc
├── build
│ ├── app.js
│ ├── controllers
│ ├── exceptions
│ ├── middlewares
│ ├── routers
│ ├── schemas
│ ├── server.js
│ ├── tests
│ └── utils
├── compose.yaml
├── coverage
│ ├── clover.xml
│ ├── coverage-final.json
│ ├── lcov-report
│ └── lcov.info
├── Dockerfile
├── eslint.config.mjs
├── jest.config.ts
├── nodemon.json
├── package.json
├── README.Docker.md
├── setup.README.md
├── src
│ ├── app.ts
│ ├── server.ts
│ ├── controllers
│ ├── exceptions
│ ├── middlewares
│ ├── routers
│ ├── schemas
│ ├── tests
│ └── utils
├── tsconfig.json
└── README.md
```
## Conclusion