# Express with TypeScript

Course: [Express.js](https://www.devtutorials.ai/learn/expressjs) · Section: Architecture and Reference
Source: https://www.devtutorials.ai/learn/expressjs/architecture-reference/express-typescript

> Setting up tsx and tsc, catching bugs with tsc --noEmit, typing params, bodies and responses, extending Request with req.user, runtime type guards and Zod, and building for production.

## Express with TypeScript

Many Express teams write their servers in TypeScript. It catches mistakes before the code runs.

- **TypeScript** = JavaScript plus **types**. A type says what shape a value has.
- `tsc` = the TypeScript compiler. It checks types and turns `.ts` into `.js`.
- `tsx` = a tool that runs `.ts` files directly, great for development.
- `@types/express` = ready-made types for Express, written by the community.
- Types exist only while you write code. At runtime, it is all plain JavaScript.

**Simple lesson:** TypeScript checks your code before it runs. It cannot check what users send.

### A Real-World Example

Think about the canteen order form.

1. The form has fixed boxes: name, item, quantity. You cannot write a poem in the quantity box.
2. The office checks the form before passing it on. Mistakes are caught early.
3. But a student can still write "100" plates. Someone must check the real value too.

The fixed boxes are types. The office check is `tsc`. Checking the real value is runtime validation.

In this lesson, you will learn:

1. How to set up an Express project with TypeScript.
2. How `tsc --noEmit` catches mistakes.
3. How to type route params, bodies and responses.
4. How to add your own fields, such as `req.user`, to the Request type.
5. Why you still validate input at runtime.
6. How to build for production.

Every lab in this course has a TypeScript version. Choose **TypeScript** in the **Language** menu in the top bar to see it in the other lessons. Fields that the app adds, such as `req.user`, live in a `types.d.ts` file there.

## Setting Up

```bash
npm install express
npm install -D typescript tsx @types/express @types/node
npx tsc --init
```

A small `tsconfig.json` for Express on Node.js 22:

```json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "strict": true,
    "outDir": "dist",
    "rootDir": "src",
    "skipLibCheck": true
  },
  "include": ["src"]
}
```

- `strict: true` turns on the most useful checks. Always use it.
- With `NodeNext`, imports still end in `.js`, even though the file is `.ts`.
- `npx tsx watch src/server.ts` runs the app and restarts on changes.

- `import type` brings in only the type. It disappears from the JavaScript.
- `req.params.id` is always a **string**. TypeScript knows this, so you must convert it.

**Simple lesson:** Describe your data with `interface`, and let TypeScript check every use.

## Catching Mistakes with tsc

`tsx` runs code without checking types. Run `tsc --noEmit` to check.

- `--noEmit` = only check, do not write `.js` files.
- Add it as `"typecheck": "tsc --noEmit"` in `package.json`, and run it in CI before every deploy.
- Errors show the file, line, column and an error code like `TS2551`.

- `item.prise` is a typo for `price`. In plain JavaScript, this gives `NaN` at runtime with no error.
- `m.id === req.params.id` compares a number with a string. It would never match, so every lookup would fail.

The editors in these labs also understand TypeScript. Hover over `req` or `item` to see its type, and look for red underlines.

**Simple lesson:** Run `tsc --noEmit` before every commit. It finds bugs you would only see later.

## Typing Params, Bodies and Responses

`Request` and `Response` take type arguments.

- `Request<Params, ResBody, ReqBody, Query>` = the shape of each part.
- `Response<Body>` = what `res.json()` is allowed to send.
- Use `RequestHandler` or a typed function for the whole handler.
- Without types, `req.body` is `any`. TypeScript then checks nothing.

- `Omit<Order, "id">` makes a new type: an order without the `id`. No need to write it twice.
- If a handler tries `res.json({ total: 5 })`, `tsc` reports that it is not an `Order`.

**Simple lesson:** Give `Request` and `Response` your types, so every route is checked.

## Adding req.user and Other Fields

Middleware often adds fields, such as `req.user` after login. TypeScript does not know about them.

- Add them to the global `Express.Request` interface with `declare global`.
- Put this in a file such as `src/express.d.ts`. Every route then sees the new field.
- Mark it optional (`user?:`), because routes without login do not have it.

- No output means **no errors**. Try changing `"staff"` to `"teacher"` in `server.ts`, then run again.

**Simple lesson:** Teach TypeScript about fields your middleware adds with `declare global`.

## Types Do Not Check Users

Types vanish when the code runs. A user can send anything.

- `req.body as NewOrder` only **tells** TypeScript to trust you. It checks nothing.
- Always validate at runtime. See **Input Processing and Validation**.
- A **type guard** is a function that checks a value and tells TypeScript its type.
- Libraries such as **Zod** do both: check the value and create the type from one schema.

```ts
import { z } from "zod";

const NewOrder = z.object({ itemId: z.number().int(), quantity: z.number().int().min(1).max(10) });
type NewOrder = z.infer<typeof NewOrder>; // the type comes from the schema

const result = NewOrder.safeParse(req.body);
if (!result.success) return res.status(400).json(result.error.flatten());
```

`as` is not a check. `req.body as NewOrder` is a promise you make to TypeScript, and users do not keep it.

**Simple lesson:** Types check your code. Validation checks your users. You need both.

## Building for Production

- Development: `tsx watch src/server.ts`.
- Production: compile once with `tsc`, then run plain JavaScript with `node`.
- Do not run `tsx` in production. Compiling at startup is slower and hides type errors.

```json
{
  "scripts": {
    "dev": "tsx watch --env-file-if-exists=.env src/server.ts",
    "typecheck": "tsc --noEmit",
    "build": "tsc",
    "start": "node dist/server.js",
    "test": "node --test --import tsx"
  }
}
```

- In Docker, run `npm run build` in the image, then `CMD ["node", "dist/server.js"]`. See **Deploying an Express App**.
- Node.js 22.18+ and 23.6+ can also run simple `.ts` files directly by removing the types. `tsc` is still needed for checking.

Easy way to remember: **tsx to run, tsc to check, tsc to build, node to start.**

**Simple lesson:** Check and compile with `tsc`. Ship plain JavaScript.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Trusting `req.body as Type` | `as` checks the data | It only silences TypeScript | Bad input reaches your database |
| Never running `tsc` | `tsx` checks types | `tsx` only removes them | Type errors reach production |
| `req.params.id` used as a number | Params have the right type | Params are always strings | `"2" === 2` is false, routes return 404 |
| Importing `./types` with no extension | TypeScript fixes imports | With NodeNext, write `./types.js` | "Cannot find module" at runtime |
| Using `any` everywhere | It is quicker | Use real types or `unknown` | TypeScript stops checking |
| Adding `req.user` with no type | TypeScript will guess | Extend `Express.Request` with `declare global` | "Property user does not exist" |
| Running `tsx` in production | It is the same as node | Build with `tsc`, start with `node` | Slower starts and hidden errors |

</div>

## Check Yourself

## Exercises

## What to Remember

- TypeScript adds types to JavaScript. Install `typescript`, `tsx`, `@types/express` and `@types/node`.
- `tsx` runs `.ts` files. `tsc --noEmit` checks them. Run the check in CI.
- Type routes with `Request<Params, ResBody, ReqBody>` and `Response<Body>`.
- Add fields like `req.user` by extending `Express.Request` with `declare global`.
- Types vanish at runtime. Validate input with type guards or Zod.
- Production: `tsc` builds to `dist`, `node dist/server.js` starts it.

Next: **Capstone: The Complete Canteen API**. You will put every part of the course together in one project.
