DEV Community

Cover image for How to Set Up a Node.js TypeScript Backend from Scratch
Elijah Darkeh Agbedam
Elijah Darkeh Agbedam

Posted on

How to Set Up a Node.js TypeScript Backend from Scratch

If you've been writing JavaScript for a while, you've probably heard people talk about TypeScript. It adds static types to JavaScript, which means you catch bugs before running your code, get better autocomplete in your editor, and write more self-documenting code. In this post, we'll set up a clean Node.js + Express backend using TypeScript from scratch, the same setup I use as a starting point for my own projects.

By the end, you'll have:

  • An Express server running TypeScript
  • Hot-reload in development (save a file and server restarts automatically)
  • A clean separation between dev and production workflows

Prerequisites: Node.js installed, basic JavaScript knowledge. That's it.

Step 1 — Initialize the Project

Create a new folder for your project and initialize a package.json:
mkdir my-backend && cd my-backend
pnpm init

Don't have pnpm? Install it with npm install -g pnpm, or just replace pnpm with npm throughout this post.

Step 2 — Install Dependencies

Install the production dependencies:
pnpm add express dotenv

  • express — the web framework we'll use to handle HTTP requests
  • dotenv — loads environment variables from a .env file (so you can store things like your port number or API keys outside your code)

Now install the development dependencies:
pnpm add -D typescript ts-node nodemon @types/node @types/express

These are only needed while building the project, not when running it in production:

  • typescript — the TypeScript compiler that turns .ts files into plain JavaScript
  • ts-node — lets you run .ts files directly without compiling first (great for development)
  • nodemon — watches your files and automatically restarts the server when you save a change
  • @types/node and @types/express — type definitions that teach TypeScript what Node.js and Express APIs look like

Step 3 — Configure TypeScript

Create a tsconfig.json file at the root of your project:

{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"rootDir": "src",
"outDir": "dist",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src"]
}

Here's what the key options mean:
target: "ES2020" - Compile to modern JavaScript (Node.js 14+ supports this)
rootDir: "src" - TypeScript source files live in the src/ folder
outDir: "dist" - Compiled JavaScript output goes into dist/
strict: true - Turns on strict type checking — catches more bugs
esModuleInterop: true - Lets you use import x from 'y' syntax with CommonJS packages
skipLibCheck: true - Skips type checking inside node_modules — avoids noisy errors

Step 4 — Configure Nodemon

Create a nodemon.json file at the root:

{
"watch": ["src"],
"ext": "ts,json",
"ignore": ["dist"],
"exec": "pnpm exec ts-node src/index.ts"
}

  • watch: ["src"] — only watch the src/ folder for changes
  • ext: "ts,json" — restart when .ts or .json files change
  • ignore: ["dist"] — don't watch the compiled output folder (this would cause an infinite restart loop)
  • exec — the command to run when a change is detected; we use pnpm exec ts-node to run TypeScript directly

Step 5 — Add Scripts to package.json

Update your package.json scripts section:

"scripts": {
"dev": "nodemon",
"build": "tsc",
"start": "node dist/index.js"
}

  • dev — starts nodemon, which runs your TypeScript directly with hot-reload
  • build — compiles your TypeScript to JavaScript in dist/
  • start — runs the compiled JavaScript (for production)

Step 6 — Set Up Environment Variables

Create a .env file at the root:
PORT=4000

Then create a .gitignore file (you don't want to commit your secrets or compiled files):

node_modules/
dist/
.env

Step 7 — Write the Server

Create a src/ folder, then create src/index.ts:

`import application from "express";
import { configDotenv } from "dotenv";

configDotenv();

const PORT = process.env.PORT || 3000;

const app = application();

app.use(application.json());

app.get("/", (_req, res) => {
res.json({ message: "Server is running" });
});

app.listen(PORT, () => {
console.log(Server is running at Port ${PORT});
});`

A few things to note:

import application from "express" — we're importing Express and calling it application. The name after import is just a variable name — you can call it anything you like (express, app, server). The important part is what comes after from.

configDotenv() — loads your .env file. This must run before you read process.env.PORT, otherwise the variable won't be set yet.

application.json() — middleware that parses incoming JSON request bodies. You'll need this for any POST/PUT endpoints that receive JSON data.

app.get("/") — a simple health-check route. When you visit http://localhost:4000/ in your browser, you'll see {"message":"Server is running"}. This is how you'll verify everything is working.

Step 8 — Run It
Start the development server:
pnpm dev

You should see:

[nodemon] 3.1.14
[nodemon] watching path(s): src/**/*
[nodemon] watching extensions: ts,json
[nodemon] starting
pnpm exec ts-node src/index.ts
Server is running at Port 4000

Open your browser and visit http://localhost:4000/ — you should see:
{ "message": "Server is running" }

When you're ready to build for production:
pnpm build - compiles TypeScript → dist/
pnpm start - runs the compiled JavaScript

What's Next

You now have a solid foundation: TypeScript, Express, hot-reload in dev, and a clean build pipeline for production. From here you can add routes, connect a database, or add authentication middleware.

Top comments (0)