Building a backend API in Node.js? Setting up the project can be a bit of a slog - and enough to put you off before you even start building that next-gen social media app.
I couldn't help but wonder: where are the quick, modern guides to spinning up a well-structured Express API?
There aren’t many. So I made one.
Let’s get straight to it.
🛠 1. Initialise your project
npm init -y
This sets up your package.json, which manages your dependencies, scripts, and project metadata. The -y flag accepts all the default values so you can move on quickly.
📦 2. Install Express and dev dependencies
npm install express
npm install --save-dev typescript supertest nodemon jest ts-jest ts-node @types/jest @types/supertest @types/express
What these do:
- express: Your HTTP server framework.
- typescript: For strongly typed JS.
- supertest: For testing your API endpoints.
- jest, ts-jest: Testing framework and TypeScript preprocessor.
- nodemon: Restarts the server on changes.
- ts-node: Runs TypeScript files directly.
- @types/...: Adds type support for testing and Express.
🧠 3. Configure TypeScript
npx tsc --init
Now replace your generated tsconfig.json with:
{
"exclude": [
"./coverage",
"./dist",
"**/*.test.ts",
"jest.config.js",
"eslint.config.mjs"
],
"ts-node": {
"transpileOnly": true,
"files": true
},
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"rootDir": "./src",
"moduleResolution": "node",
"checkJs": true,
"outDir": "./dist",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"skipLibCheck": true
}
}
The linter may freak out, until you create your first .ts file, which we will do now. ;)
🧱 4. Create your Express app
Here's the directory structure for our app:
mkdir -p src/routes && touch src/app.ts src/app.test.ts src/server.ts src/routes/user.routes.ts src/routes/user.routes.test.ts
src/
├── app.ts
├── app.test.ts
├── server.ts
└── routes/
└── user.routes.ts
└── user.routes.test.ts
src/server.ts
import app from './app';
const PORT: number = 5050;
app.listen(PORT, (): void => {
// eslint-disable-next-line no-console
console.log(`Server is running on ${PORT}...`);
});
src/app.ts
import express, { Application, Request, Response} from 'express';
import { router as userRoutes } from './routes/user.routes';
const app: Application = express();
app.use('/users', userRoutes);
app.use('/', (req: Request, res: Response): void => {
res.json({ message: "Miley, what's good?" });
})
export default app;
src/routes/user.routes.ts
import { Router, Request, Response } from 'express';
const router = Router();
router.get('/', (req: Request, res: Response): void => {
const users = ['Nicki', 'Ariana', 'Lana', 'Miley'];
res.status(200).send(users);
});
export { router };
🧪 5. Set up testing
npx ts-jest config:init
Replace the generated jest.config.js's contents with:
export const preset = 'ts-jest'
export const testEnvironment = 'node'
This sets up Jest to use the TypeScript compiler.
🧷 6. Add some tests
src/app.test.ts
import request from 'supertest';
import app from './app';
describe('Test app.ts', () => {
test('Is alive route', async () => {
const res = await request(app).get('/');
expect(res.body).toEqual({ message: "Miley, what's good?" });
});
});
src/routes/user.routes.test.ts
import request from 'supertest';
import app from '../app';
describe('User routes', () => {
test('Get all users', async () => {
const res = await request(app).get('/users');
expect(res.body).toEqual(['Nicki', 'Ariana', 'Lana', 'Miley']);
});
});
⚙️ 7. Update package.json scripts
Update the scripts section:
"scripts": {
"test": "jest --coverage",
"dev": "nodemon ./src/server.ts",
"build": "tsc",
"lint": "eslint src/**/*.ts",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix"
}
(Don't worry about the lint scripts for now, we will set this up in the following step.)
While you're at it, remove "type":"module"
from package.json.
Give this a go: npm run test
🧹 8. Set up ESLint + Prettier
npm install -D eslint @eslint/js @types/eslint__js typescript-eslint eslint-plugin-prettier eslint-config-prettier
Now create the ESLint config file touch eslint.config.mjs
:
import js from '@eslint/js'
import tseslint from 'typescript-eslint'
import eslintPluginPrettier from 'eslint-plugin-prettier'
import prettier from 'eslint-config-prettier'
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
project: './tsconfig.eslint.json',
sourceType: 'module',
},
},
plugins: {
prettier: eslintPluginPrettier,
},
rules: {
'no-unused-vars': 'error',
'no-undef': 'off',
'prefer-const': 'error',
'no-console': 'warn',
'no-debugger': 'warn',
'prettier/prettier': [
'error',
{
singleQuote: true,
semi: true,
trailingComma: 'all',
printWidth: 100,
tabWidth: 2,
},
],
},
},
prettier,
{
ignores: ['dist/**', 'node_modules/**', 'coverage/**'],
},
]
Let's also add TypeScript config for ESLint
Create a touch tsconfig.eslint.json
:
{
"extends": "./tsconfig.json",
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist"]
}
You now have:
A strongly typed Express API 💪
Tests with Jest + Supertest 🧪
Auto formatting & linting with ESLint + Prettier ✨
You’re all set to build something real - without battling the boilerplate every time.
Thank you, please like and subscribe! 💾
Voilà! If you enjoy this content, please consider supporting my efforts. Your generosity fuels more of what you love! 🩷
I'm János, I write about Software, coding, and technology.
Checkout my portfolio
Top comments (0)