强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

20 - Node.js + TypeScript

Node.js + TypeScript

项目初始化

# 创建项目
mkdir my-node-ts-app && cd my-node-ts-app
npm init -y

# 安装依赖
npm install -D typescript @types/node ts-node

# 初始化 tsconfig
npx tsc --init

tsconfig.json 推荐配置

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

package.json 配置

{
  "name": "my-node-ts-app",
  "version": "1.0.0",
  "type": "module",
  "main": "./dist/index.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "scripts": {
    "build": "tsc",
    "start": "node dist/index.js",
    "dev": "tsx watch src/index.ts",
    "typecheck": "tsc --noEmit"
  }
}

ts-node

ts-node 可以直接运行 TypeScript 文件:

# 安装
npm install -D ts-node

# 运行单个文件
npx ts-node src/index.ts

# 使用 tsconfig-paths 支持路径别名
npm install -D tsconfig-paths
npx ts-node -r tsconfig-paths/register src/index.ts

ts-node 配置

// tsconfig.json
{
  "ts-node": {
    "transpileOnly": true,
    "compilerOptions": {
      "module": "CommonJS"
    }
  },
  "compilerOptions": {
    "target": "ES2022",
    "module": "CommonJS",
    "esModuleInterop": true,
    "strict": true
  }
}

tsx(推荐)

tsx 是更现代的 TypeScript 运行时,基于 esbuild:

# 安装
npm install -D tsx

# 运行
npx tsx src/index.ts

# 监听模式
npx tsx watch src/index.ts
工具速度ESM 支持配置复杂度
ts-node较慢需配置中等
tsx极快原生简单
tsc + node最慢原生简单

Node.js 内置类型

fs 模块

import fs from "node:fs";
import fsPromises from "node:fs/promises";

// 同步读取
const content: string = fs.readFileSync("./data.txt", "utf-8");

// 异步读取
const data: string = await fsPromises.readFile("./data.txt", "utf-8");

// 写入文件
await fsPromises.writeFile("./output.txt", "Hello", "utf-8");

// 检查文件存在
const exists: boolean = fs.existsSync("./data.txt");

// 创建目录
await fsPromises.mkdir("./output", { recursive: true });

// 读取目录
const files: string[] = await fsPromises.readdir("./src");

// 文件信息
const stat: fs.Stats = await fsPromises.stat("./data.txt");
console.log(stat.size, stat.mtime);

path 模块

import path from "node:path";

const joined: string = path.join("src", "utils", "helpers.ts");
// src/utils/helpers.ts

const resolved: string = path.resolve("./src/index.ts");
// /absolute/path/to/src/index.ts

const ext: string = path.extname("file.ts"); // ".ts"
const base: string = path.basename("/path/to/file.ts"); // "file.ts"
const dir: string = path.dirname("/path/to/file.ts"); // "/path/to"
const parsed = path.parse("/path/to/file.ts");
// { root: '/', dir: '/path/to', base: 'file.ts', ext: '.ts', name: 'file' }

http 模块

import http from "node:http";

interface RequestHandler {
  (req: http.IncomingMessage, res: http.ServerResponse): void;
}

const server = http.createServer((req, res) => {
  res.statusCode = 200;
  res.setHeader("Content-Type", "application/json");
  res.end(JSON.stringify({ message: "Hello" }));
});

server.listen(3000, () => {
  console.log("Server running on port 3000");
});

process 和环境变量

// 环境变量类型
interface Env {
  NODE_ENV: "development" | "production" | "test";
  PORT: string;
  DATABASE_URL: string;
  API_KEY: string;
}

function getEnv(): Env {
  const required = ["NODE_ENV", "PORT", "DATABASE_URL", "API_KEY"] as const;

  for (const key of required) {
    if (!process.env[key]) {
      throw new Error(`Missing environment variable: ${key}`);
    }
  }

  return {
    NODE_ENV: process.env.NODE_ENV as Env["NODE_ENV"],
    PORT: process.env.PORT!,
    DATABASE_URL: process.env.DATABASE_URL!,
    API_KEY: process.env.API_KEY!
  };
}

// 命令行参数
const args: string[] = process.argv.slice(2);
console.log("Arguments:", args);

Express + TypeScript

npm install express
npm install -D @types/express
import express, { Request, Response, NextFunction } from "express";

const app = express();
app.use(express.json());

// 路由参数类型
interface UserParams {
  id: string;
}

interface UserBody {
  name: string;
  email: string;
}

interface UserQuery {
  page?: string;
  limit?: string;
}

// 路由处理
app.get("/api/users", (req: Request<{}, {}, {}, UserQuery>, res: Response) => {
  const { page = "1", limit = "10" } = req.query;
  res.json({ page: Number(page), limit: Number(limit) });
});

app.get("/api/users/:id", (req: Request<UserParams>, res: Response) => {
  const { id } = req.params;
  res.json({ id: Number(id) });
});

app.post("/api/users", (req: Request<{}, {}, UserBody>, res: Response) => {
  const { name, email } = req.body;
  res.json({ name, email });
});

// 错误处理中间件
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  console.error(err.stack);
  res.status(500).json({ error: err.message });
});

app.listen(3000, () => {
  console.log("Server running on port 3000");
});

类型安全的路由封装

import express, { Request, Response, Router } from "express";

type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

interface RouteConfig<
  TParams = any,
  TBody = any,
  TQuery = any,
  TResponse = any
> {
  method: HttpMethod;
  path: string;
  handler: (
    req: Request<TParams, TResponse, TBody, TQuery>,
    res: Response<TResponse>
  ) => Promise<void> | void;
}

function createRouter(routes: RouteConfig[]): Router {
  const router = express.Router();

  for (const route of routes) {
    const method = route.method.toLowerCase() as "get" | "post" | "put" | "delete";
    router[method](route.path, route.handler);
  }

  return router;
}

// 使用
const userRoutes = createRouter([
  {
    method: "GET",
    path: "/",
    handler: async (req, res) => {
      res.json([]);
    }
  },
  {
    method: "POST",
    path: "/",
    handler: async (req, res) => {
      res.json({ id: 1, ...req.body });
    }
  }
]);

Fastify + TypeScript

npm install fastify
import Fastify from "fastify";

const fastify = Fastify({ logger: true });

// 路由 Schema(类型 + 验证)
fastify.post<{ Body: CreateUserDto }>("/api/users", {
  schema: {
    body: {
      type: "object",
      required: ["name", "email"],
      properties: {
        name: { type: "string" },
        email: { type: "string", format: "email" }
      }
    }
  }
}, async (request, reply) => {
  const { name, email } = request.body;
  return { id: 1, name, email };
});

interface CreateUserDto {
  name: string;
  email: string;
}

数据库操作

Prisma

npm install prisma @prisma/client
npx prisma init
// prisma/schema.prisma
// model User {
//   id    Int     @id @default(autoincrement())
//   name  String
//   email String  @unique
//   posts Post[]
// }

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

// 类型安全的数据库操作
async function getUsers() {
  const users = await prisma.user.findMany({
    include: { posts: true }
  });
  return users; // 自动推断类型
}

async function createUser(data: { name: string; email: string }) {
  return prisma.user.create({ data });
}

类型安全的 SQL 查询

import Database from "better-sqlite3";

interface User {
  id: number;
  name: string;
  email: string;
}

const db = new Database("app.db");

// 类型安全的查询
function getUser(id: number): User | undefined {
  return db.prepare("SELECT * FROM users WHERE id = ?").get(id) as User | undefined;
}

function getAllUsers(): User[] {
  return db.prepare("SELECT * FROM users").all() as User[];
}

function insertUser(user: Omit<User, "id">): number {
  const result = db
    .prepare("INSERT INTO users (name, email) VALUES (?, ?)")
    .run(user.name, user.email);
  return result.lastInsertRowid as number;
}

业务场景:API 服务骨架

import express from "express";
import { PrismaClient } from "@prisma/client";

// 配置
const config = {
  port: Number(process.env.PORT) || 3000,
  nodeEnv: process.env.NODE_ENV || "development"
};

// 数据库
const prisma = new PrismaClient();

// 应用
const app = express();
app.use(express.json());

// 中间件类型
type AsyncHandler = (
  req: express.Request,
  res: express.Response,
  next: express.NextFunction
) => Promise<void>;

// 异步包装器
function asyncHandler(fn: AsyncHandler): express.RequestHandler {
  return (req, res, next) => {
    fn(req, res, next).catch(next);
  };
}

// 路由
app.get(
  "/api/users",
  asyncHandler(async (req, res) => {
    const users = await prisma.user.findMany();
    res.json(users);
  })
);

app.get(
  "/api/users/:id",
  asyncHandler(async (req, res) => {
    const user = await prisma.user.findUnique({
      where: { id: Number(req.params.id) }
    });
    if (!user) {
      res.status(404).json({ error: "User not found" });
      return;
    }
    res.json(user);
  })
);

// 全局错误处理
app.use((err: Error, req: express.Request, res: express.Response, _next: express.NextFunction) => {
  console.error(err);
  res.status(500).json({
    error: config.nodeEnv === "production" ? "Internal Server Error" : err.message
  });
});

// 启动
app.listen(config.port, () => {
  console.log(`Server running on port ${config.port}`);
});

注意事项

  1. @types/node——安装 Node.js 的类型定义
  2. ESM vs CJS——根据项目需求选择模块系统
  3. tsx 比 ts-node 更快——推荐使用 tsx 作为开发运行时
  4. 环境变量类型——创建类型定义确保环境变量存在
  5. 数据库类型——使用 Prisma 等 ORM 可以获得类型安全的数据库操作

扩展阅读