Home/Learn/JavaScript & TypeScript/TypeScript on the Server

TypeScript on the Server

Advanced
TypeScript

Node with TypeScript — module resolution, environment variables, and where a typed backend genuinely pays off in a full-stack app.

Overview

A full-stack JavaScript engineer is expected to be credible on the server too, and Node with TypeScript has its own set of sharp edges: two module systems that do not quite agree, environment variables that are all `string | undefined`, and a runtime that has changed how it handles TypeScript three times in as many years. The payoff is real though — when both ends of a request share the same types, a renamed field is a compile error rather than a support ticket.

Modules and Setup

CommonJS and ESM, and the config that decides which you get.

NodeNext, .js extensions, and no __dirname
// package.json
{ "type": "module" }              // .js files are ESM
// without it, .js is CommonJS

// tsconfig for modern Node
{
  "compilerOptions": {
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "target": "ES2022",
    "types": ["node"]
  }
}

// With NodeNext, relative ESM imports need the extension —
// and it is the OUTPUT extension, which trips everyone up:
import { score } from './scoring.js'     // even though the file is .ts

// Running it
node --experimental-strip-types src/index.ts    // Node 22+
tsx src/index.ts                                 // the common dev tool
// Both strip types without checking — still run tsc --noEmit separately.

// __dirname does not exist in ESM:
import { fileURLToPath } from 'node:url'
const __dirname = path.dirname(fileURLToPath(import.meta.url))

Environment and Config

process.env is string | undefined for everything. Validate it once at startup.

Parse the environment once, at startup
process.env.PORT              // string | undefined — always
process.env.PORT + 1          // '30001' if you forget

// Validate the whole environment at boot, and fail loudly
import { z } from 'zod'

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

export const env = Env.parse(process.env)
// env.PORT is number, env.DATABASE_URL is a validated string,
// and a missing secret crashes on startup rather than on the
// first login attempt at 2am.

// Never log the parsed object — it holds secrets.

Sharing Types Across the Stack

The actual advantage of TypeScript on both ends.

One schema package, both ends
// A shared package, imported by both server and client
// packages/shared/src/problem.ts
export const ProblemSchema = z.object({ slug: z.string(), ... })
export type Problem = z.infer<typeof ProblemSchema>

// server: validates outgoing shape
res.json(ProblemSchema.parse(row))

// client: same type, no duplication
import type { Problem } from '@aicancode/shared'

// Rename 'slug' to 'id' in one file and both ends fail to compile.
// That is the whole argument for a TypeScript backend.

// Note for a Python backend: this does not apply.
// With FastAPI, the equivalent link is the generated OpenAPI client —
// Pydantic models on one side, generated TS types on the other.
// Same principle, different mechanism.

Key Points to Remember

  • 1"type": "module" plus module: NodeNext is the modern setup; relative ESM imports need the output .js extension
  • 2__dirname does not exist in ESM — derive it from import.meta.url
  • 3Every process.env value is string | undefined; parse and coerce the whole environment once at startup
  • 4tsx and node --experimental-strip-types run TypeScript without checking it, so keep tsc --noEmit in CI
  • 5A shared schema package makes a renamed field a compile error on both ends — the main reason to run TypeScript on the server

Interview Questions

Sign in to ask Aria
1

Why do ESM imports in a NodeNext TypeScript project use a .js extension for .ts files?

Hard
2

What type does process.env.PORT have, and how should configuration be handled?

Medium
3

What is the practical benefit of running TypeScript on both the client and the server?

Medium

Ask Aria about TypeScript on the Server

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…