π π 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
.nvmrc
and 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 install
followed bynvm use
. -
Add, commit & push your changes.
NodeJS project setup
-
Do an
npm init
and answer all the questions. -
Create a
src
folder. -
Add, commit & push your changes.
Typescript setup
-
Run
npm i -D typescript
. -
Run
npx tsc --init
. -
Uncomment
rootDir
intsconfig.json
& set it's value to "./src". -
Uncomment
outDir
intsconfig.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-exact
flag:npm i -D --save-exact prettier
. -
Create an empty
.prettierrc
file and add empty object{}
to it. -
Create an empty
.prettierignore
file 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.js
ans 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-commit
file 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
.env
file & put all your secrets as key-value pairs. -
Optionally, also create a
.env.example
file & put all your secrets but with fake values. -
Create
src/config/index.ts
and 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.ts
file inside thesrc
folder. -
Install express & it's type declarations:
npm i express
&npm i -D @types/express
-
Put minimal code inside the
app.ts
and 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.ts
and 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.json
to:"dev": "nodemon src/server.ts",
-
Install
nodemon
&ts-node
npm i -D ts-node nodemon
Try running
npm run dev
to 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.ts
and 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-errors
npm 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.js
file, rename it's extension to.mjs
and 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.ts
orapp.test.ts
at 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 test
to 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.