DEV Community

Cover image for Setting Up a Modern Express API with TypeScript, Jest, ESLint, Prettier - Quick guide 2025
János K.
János K.

Posted on • Edited on

1

Setting Up a Modern Express API with TypeScript, Jest, ESLint, Prettier - Quick guide 2025

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
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}...`);
});

Enter fullscreen mode Exit fullscreen mode

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;

Enter fullscreen mode Exit fullscreen mode

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 };
Enter fullscreen mode Exit fullscreen mode

🧪 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'
Enter fullscreen mode Exit fullscreen mode

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?" });
  });
});
Enter fullscreen mode Exit fullscreen mode

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']);
  });
});

Enter fullscreen mode Exit fullscreen mode

⚙️ 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"
}
Enter fullscreen mode Exit fullscreen mode

(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/**'],
  },
]

Enter fullscreen mode Exit fullscreen mode

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"]
}
Enter fullscreen mode Exit fullscreen mode

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! 🩷


Buy me a coffee


I'm János, I write about Software, coding, and technology.
Checkout my portfolio

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

Postgres on Neon - Get the Free Plan

No credit card required. The database you love, on a serverless platform designed to help you build faster.

Get Postgres on Neon

Join the Runner H "AI Agent Prompting" Challenge: $10,000 in Prizes for 20 Winners!

Runner H is the AI agent you can delegate all your boring and repetitive tasks to - an autonomous agent that can use any tools you give it and complete full tasks from a single prompt.

Check out the challenge

DEV is bringing live events to the community. Dismiss if you're not interested. ❤️