π π Checklist for the express project initial setup
- [x] Git setup
- [x] Node version manager setup
- [x] NodeJS project setup
- [x] Typescript setup
- [x] Prettier setup
- [x] Eslint setup
- [x] Git hooks setup
- [x] Application config setup
- [x] ExpressJS app setup
- [x] Logger setup
- [x] Error handling setup
- [x] Tests setup
Start by cd'ing into your template folder
Git setup
-
Setting up .gitignore: you can either get it manually from repo OR if you're using VSCode, use extension to generate it for you.
-
Do a
git init, add & then commit your changes. -
Push this repository to the remote.
Node version manager setup
-
Use the latest LTS version of the time.
-
Create a file called
.nvmrcand write the version you're using eg:v20.15.0 -
Then open a terminal and run
nvm use, if it fails with an error saying "version not yet installed" then donvm installfollowed bynvm use. -
Add, commit & push your changes.
NodeJS project setup
-
Do an
npm initand answer all the questions. -
Create a
srcfolder. -
Add, commit & push your changes.
Typescript setup
-
Run
npm i -D typescript. -
Run
npx tsc --init. -
Uncomment
rootDirintsconfig.json& set it's value to "./src". -
Uncomment
outDirintsconfig.json& set it's value to "./dist". -
Install types for node
npm i -D @types/node. -
Add, commit & push your changes.
Prettier setup (follow official docs)
-
Install it with the
--save-exactflag:npm i -D --save-exact prettier. -
Create an empty
.prettierrcfile and add empty object{}to it. -
Create an empty
.prettierignorefile and add:build coverage -
Now, you can manually check for formatting issues by running:
npx prettier . --check& fix them all by running:npx prettier . --write. -
You can add in
package.json"scripts" to aid in CI/CD pipelines:"format:check": "prettier . --check", "format:fix": "prettier . --write" -
Optionally add the following config to
.prettierrc(and more):{ "arrowParens": "avoid", "printWidth": 80, "tabWidth": 2, "semi": false, "singleQuote": true, "jsxSingleQuote": true, "trailingComma": "none", "proseWrap": "always" } -
Add, commit & push your changes.
Eslint setup (follow official docs)
-
Run
npm install --save-dev eslint @eslint/js @types/eslint__js typescript typescript-eslint eslint-config-prettier -
Create a file
.eslint-config.jsans paste the following:// @ts-check import eslint from "@eslint/js"; import tseslint from "typescript-eslint"; import eslintConfigPrettier from "eslint-config-prettier"; export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommendedTypeChecked, eslintConfigPrettier, { languageOptions: { parserOptions: { project: true, tsconfigRootDir: import.meta.dirname, }, }, }, { ignores: ["dist/*", "eslint.config.js"], }, { rules: { "no-console": "error", }, } ); -
Add the followings to scripts in
package.json:"lint": "eslint .", "lint:fix": "eslint . --fix" -
Add, commit & push your changes.
Git hooks setup
-
Install husky
npm install --save-dev husky -
Run
npx husky init -
Modify
.husky/pre-commitfile to whatever you want to run before every commit. We'll replace all it's content with:npx lint-staged -
Install lint-staged to only run pre-commit hooks on staged code:
npm install --save-dev lint-staged -
Add this to your
package.json:"lint-staged": { "*.ts": [ "npm run lint:fix", "npm run format:fix" ] } -
Add, commit & push your changes.
Application config setup
-
Install dotenv:
npm i dotenv -
Create a
.envfile & put all your secrets as key-value pairs. -
Optionally, also create a
.env.examplefile & put all your secrets but with fake values. -
Create
src/config/index.tsand paste the following(add your own values):import { config } from "dotenv"; config(); const { PORT, NODE_ENV } = process.env; // later if you want to change the way you're getting env variables (eg. from a file), you can just change this line export const Config = { PORT, NODE_ENV, }; -
Add, commit & push your changes.
ExpressJS App Setup
-
Create an
app.tsfile inside thesrcfolder. -
Install express & it's type declarations:
npm i express&npm i -D @types/express -
Put minimal code inside the
app.tsand export the app.import express from "express"; const app = express(); app.get("/", (req, res) => { res.send("Welcome to Auth service"); }); export default app;
If you see ESLint, try ctrl+shift+p -> Restart ESLint server.
-
Create a new file
src/server.tsand import the app there.import app from "./app"; import { Config } from "./config"; const startServer = () => { const PORT = Config.PORT; try { // eslint-disable-next-line no-console app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); } catch (error) { // eslint-disable-next-line no-console console.error(error); process.exit(1); } }; startServer(); -
Change the dev script in
package.jsonto:"dev": "nodemon src/server.ts", -
Install
nodemon&ts-nodenpm i -D ts-node nodemonTry running
npm run devto see if it works. -
Add, commit & push your changes.
Logger setup
-
Install Winston & its types.
npm i winston npm i -D @types/winston -
Create
src/config/logger.tsand paste:import winston from "winston"; import { Config } from "."; const logger = winston.createLogger({ level: "info", defaultMeta: { serviceName: "auth-service", }, transports: [ new winston.transports.File({ dirname: "logs", filename: "combined.log", level: "info", silent: Config.NODE_ENV !== "production", }), new winston.transports.File({ dirname: "logs", filename: "error.log", level: "error", silent: Config.NODE_ENV !== "production", }), new winston.transports.Console({ level: "info", format: winston.format.combine( winston.format.timestamp(), winston.format.json() ), silent: Config.NODE_ENV !== "production", }), ], }); export default logger;
In your server.ts file, replace the console.log with logger.info and console.error with logger.error. The file will now look like this:
import app from "./app";
import { Config } from "./config";
import logger from "./config/logger";
const startServer = () => {
const PORT = Config.PORT;
try {
app.listen(PORT, () => logger.info(`Listening on port ${PORT}`));
} catch (error: unknown) {
if (error instanceof Error) {
logger.error(error.message);
setTimeout(() => process.exit(1), 1000);
}
}
};
startServer();
- Add, commit & push your changes.
Error handling setup
-
Install
http-errorsnpm i http-errors -
Add the following global error handler as the last middleware in
app.ts:// eslint-disable-next-line @typescript-eslint/no-unused-vars app.use( (err: HttpError, req: Request, res: Response, next: NextFunction) => { logger.error(err.message); const statusCode = err.statusCode || 500; res.status(statusCode).json({ errors: [ { type: err.name, msg: err.message, path: "", location: "", }, ], }); } ); -
Add, commit & push your changes.
Tests setup
-
Run:
npm i -D jest ts-jest @types/jest supertest @types/supertest npx ts-jest config:init -
Step 1 will result in a
jest.config.jsfile, rename it's extension to.mjsand replace it's contents with:/** @type {import('ts-jest').JestConfigWithTsJest} */ export default { preset: "ts-jest", testEnvironment: "node", }; -
In your
package.json, add another script:"test": "jest --watch --runInBand" -
Create a new file called either
app.spec.tsorapp.test.tsat the root of your project to test the jest setup.import request from "supertest"; import app from "./src/app"; describe("App", () => { it("should return 200 status", async () => { const response = await request(app).get("/").send(); expect(response.statusCode).toBe(200); }); });Now run
npm run testto check if it runs. It should run as well as pass. -
Add, commit & push your changes.
That's it! Your template is ready, you can use it for all your future projects.