iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

TypeScript in Node

TypeScript on Node has changed a lot - ts-node, tsx, and bun all work, but esbuild + tsc strict types is the steady-state.

Node + TypeScript setup

EXAMPLE
// 1. Install
// npm i -D typescript @types/node tsx

// 2. tsconfig.json - strict baseline
{
  'compilerOptions': {
    'target': 'ES2022',
    'module': 'NodeNext',
    'moduleResolution': 'NodeNext',
    'lib': ['ES2022'],
    'strict': true,
    'noUncheckedIndexedAccess': true,
    'exactOptionalPropertyTypes': true,
    'noImplicitOverride': true,
    'noFallthroughCasesInSwitch': true,
    'forceConsistentCasingInFileNames': true,
    'isolatedModules': true,
    'declaration': true,
    'sourceMap': true,
    'outDir': 'dist',
    'rootDir': 'src'
  },
  'include': ['src/**/*'],
  'exclude': ['node_modules', 'dist']
}

// 3. package.json
{
  'type': 'module',
  'main': 'dist/index.js',
  'scripts': {
    'dev': 'tsx watch src/index.ts',
    'build': 'tsc',
    'start': 'node dist/index.js',
    'typecheck': 'tsc --noEmit'
  }
}

// 4. ESM-friendly imports - always include the .js extension
// Yes, in TypeScript source, you write the .js extension for relative imports.
import { db } from './db.js';
import { logger } from './logger.js';

// 5. Environment variables - validate at boot
import { z } from 'zod';

const Env = z.object({
  PORT: z.coerce.number().default(3000),
  DATABASE_URL: z.string().url(),
  NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
});

export const env = Env.parse(process.env);

// 6. Type-safe routes - express with zod
import express, { Request, Response } from 'express';
import { z } from 'zod';

const router = express.Router();

const CreateUser = z.object({ email: z.string().email(), name: z.string().min(1) });
type CreateUser = z.infer<typeof CreateUser>;

router.post('/users', async (req: Request, res: Response) => {
  const parsed = CreateUser.safeParse(req.body);
  if (!parsed.success) return res.status(400).json(parsed.error.flatten());
  const user: CreateUser = parsed.data;
  // ... create user ...
  res.status(201).json(user);
});

// 7. Production builds with tsc; reach for esbuild or tsup only if cold-start matters

Why it matters

Strict types catch bugs you would not see in tests. Validate env vars with Zod at boot - failure is loud and immediate. tsx for dev, tsc for build; reach for bundlers only when cold-start latency is a problem.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// tsconfig.json compilerOptions:
// "target": "ES2022", "module": "NodeNext", "strict": true
// Run with tsx: npx tsx src/index.ts
Try it Yourself »

Discussion

Loading…