https://nodejs.org/en/learn
https://nodejs.org/api/
https://nodejs.org/api/esm.html
https://nodejs.org/api/test.html
https://nodejs.org/api/process.html
https://docs.npmjs.com/cli/v11/configuring-npm/package-json
https://eslint.org/docs/latest/use/getting-started

1. Important Points#

Node.js 是 JavaScript runtime:
    built on V8
    event loop / non-blocking IO
    good for API / worker / CLI / realtime apps
    ecosystem uses npm packages

适合:
    HTTP API
    BFF / gateway
    queue worker
    CLI tool
    realtime websocket service
    serverless function

不适合:
    CPU-heavy compute without worker thread / native addon
    blocking sync IO in hot path
    unbounded memory workload

核心原则:
    use Node.js LTS
    use ESM for new projects
    one package manager per project
    use npm ci in CI
    validate external input
    reuse clients and connection pools
    log structured JSON in production
mental model:
    JavaScript code
        -> Node.js runtime
        -> event loop handles async IO
        -> libuv handles many OS async operations
        -> npm manages packages

2. Environment Setup#

versions#

recommended:
    Node.js LTS
    npm bundled with Node.js

check:
    node -v
    npm -v

create project#

mkdir order-api
cd order-api

npm init -y

npm install express dotenv zod pino
npm install -D eslint @eslint/js globals vitest
dependencies:
    express:
        HTTP server framework

    dotenv:
        local .env loading

    zod:
        runtime validation

    pino:
        structured JSON logger

3. Project Structure#

order-api
├── package.json
├── eslint.config.mjs
├── .env.example
├── .env.local
├── src
│   ├── main.js
│   ├── app.js
│   ├── config
│   │   └── config.js
│   ├── logger
│   │   └── logger.js
│   ├── errors
│   │   └── AppError.js
│   ├── orders
│   │   ├── OrderRepository.js
│   │   ├── OrderService.js
│   │   └── OrderRoutes.js
│   └── shared
│       └── result.js
└── test
    └── OrderService.test.js
structure rules:
    main.js:
        process entrypoint

    app.js:
        express app composition

    config/:
        environment config loading and validation

    logger/:
        logger singleton / factory

    errors/:
        application errors

    feature folder:
        repository
        service
        routes/controller

4. Package / Dependency Management#

package.json#

{
  "name": "order-api",
  "version": "1.0.0",
  "type": "module",
  "private": true,
  "scripts": {
    "dev": "node --watch src/main.js",
    "start": "node src/main.js",
    "test": "node --test",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "check": "npm run lint && npm test"
  },
  "dependencies": {
    "dotenv": "latest",
    "express": "latest",
    "pino": "latest",
    "zod": "latest"
  },
  "devDependencies": {
    "@eslint/js": "latest",
    "eslint": "latest",
    "globals": "latest",
    "vitest": "latest"
  }
}
important:
    type=module:
        use ESM import/export

    private=true:
        prevents accidental npm publish

    package-lock.json:
        commit it
        use npm ci in CI

import / export#

// createOrderId.js
import { randomUUID } from "node:crypto";

export function createOrderId() {
  return `o-${randomUUID()}`;
}
import { createOrderId } from "./createOrderId.js";
ESM note:
    local import path should include .js extension

5. Syntax You Need Most#

variables#

const orderId = "o-1001";
let status = "PENDING";

status = "PAID";
rule:
    use const by default
    use let only when reassignment is needed
    avoid var

object / array#

const order = {
  orderId: "o-1001",
  userId: "u-1001",
  amount: 99.9,
  status: "PENDING"
};

const items = [
  { sku: "sku-1001", quantity: 1 },
  { sku: "sku-1002", quantity: 2 }
];

destructuring / spread#

const { orderId, userId } = order;

const paidOrder = {
  ...order,
  status: "PAID",
  paidAt: new Date().toISOString()
};

function#

function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.quantity * item.price, 0);
}

const isPaid = (order) => order.status === "PAID";

async / await#

async function findOrder(orderId) {
  return orderRepository.findById(orderId);
}

class#

export class OrderService {
  constructor(repository) {
    this.repository = repository;
  }

  async markPaid(orderId) {
    const order = await this.repository.findById(orderId);

    if (order === null) {
      throw new Error("order not found");
    }

    return this.repository.save({
      ...order,
      status: "PAID"
    });
  }
}

module builtin#

import { readFile } from "node:fs/promises";
import { createServer } from "node:http";
import { randomUUID } from "node:crypto";
rule:
    use node: prefix for Node.js built-in modules

6. Error Handling#

app error#

export class AppError extends Error {
  constructor(message, code, statusCode) {
    super(message);
    this.name = "AppError";
    this.code = code;
    this.statusCode = statusCode;
  }
}

async error#

export class OrderService {
  constructor(repository) {
    this.repository = repository;
  }

  async markPaid(orderId) {
    const order = await this.repository.findById(orderId);

    if (order === null) {
      throw new AppError("order not found", "ORDER_NOT_FOUND", 404);
    }

    if (order.status !== "PENDING") {
      throw new AppError("order status is not pending", "INVALID_ORDER_STATUS", 409);
    }

    return this.repository.save({
      ...order,
      status: "PAID",
      paidAt: new Date().toISOString()
    });
  }
}
rules:
    expected business error -> AppError
    unexpected error -> log stack and return generic 500
    never expose raw stack to client

7. Testing#

node:test#

import test from "node:test";
import assert from "node:assert/strict";

test("marks pending order as paid", async () => {
  const repository = new InMemoryOrderRepository([
    {
      orderId: "o-1001",
      userId: "u-1001",
      amount: 99.9,
      status: "PENDING"
    }
  ]);

  const service = new OrderService(repository);
  const order = await service.markPaid("o-1001");

  assert.equal(order.status, "PAID");
});
npm test
what to test:
    service business logic
    validation
    repository
    HTTP routes
    error mapping

8. Lint#

install#

npm install -D eslint @eslint/js

eslint.config.mjs#

import eslint from "@eslint/js";
import globals from "globals";

export default [
  {
    ignores: [
      "node_modules/**",
      "coverage/**",
      "dist/**"
    ]
  },
  eslint.configs.recommended,
  {
    files: ["src/**/*.js", "test/**/*.js"],
    languageOptions: {
      ecmaVersion: "latest",
      sourceType: "module",
      globals: {
        ...globals.node
      }
    },
    rules: {
      "no-unused-vars": [
        "warn",
        {
          "argsIgnorePattern": "^_",
          "varsIgnorePattern": "^_"
        }
      ],
      "no-console": "off"
    }
  }
];

run#

npm run lint
npm run lint:fix
lint vs test:
    lint:
        catches code quality and common mistakes

    test:
        checks behavior

9. Configuration By Environment#

files#

config organization:
    .env.example
        committed template

    .env.local
        local development
        not committed

    .env.test
        test defaults

    staging / prod:
        injected by deployment platform

.env.example#

NODE_ENV=local
PORT=3000
LOG_LEVEL=debug
DATABASE_URL=postgres://user:password@localhost:5432/order
PAYMENT_API_URL=http://localhost:8080

typed-like validation with zod#

import "dotenv/config";
import { z } from "zod";

const configSchema = z.object({
  NODE_ENV: z.enum(["local", "dev", "test", "staging", "prod"]).default("local"),
  PORT: z.coerce.number().int().positive().default(3000),
  LOG_LEVEL: z.enum(["trace", "debug", "info", "warn", "error"]).default("info"),
  DATABASE_URL: z.string().url(),
  PAYMENT_API_URL: z.string().url()
});

const parsed = configSchema.safeParse(process.env);

if (!parsed.success) {
  console.error(parsed.error.flatten().fieldErrors);
  process.exit(1);
}

export const config = {
  env: parsed.data.NODE_ENV,
  port: parsed.data.PORT,
  logLevel: parsed.data.LOG_LEVEL,
  databaseUrl: parsed.data.DATABASE_URL,
  paymentApiUrl: parsed.data.PAYMENT_API_URL
};

10. Logging#

import pino from "pino";
import { config } from "../config/config.js";

export const logger = pino({
  level: config.logLevel,
  base: {
    service: "order-api",
    env: config.env
  }
});
rules:
    JSON logs in production
    pretty logs only local
    include request_id / trace_id
    redact secrets

11. Build / Run / Debug#

dev#

npm run dev

test#

npm test

lint#

npm run lint

production#

npm run start

debug#

node --inspect-brk src/main.js
production:
    run with NODE_ENV=production
    use process manager or container orchestrator
    configure graceful shutdown

12. Common Project Patterns#

repository#

export class InMemoryOrderRepository {
  constructor(initialOrders = []) {
    this.orders = new Map();

    for (const order of initialOrders) {
      this.orders.set(order.orderId, order);
    }
  }

  async findById(orderId) {
    return this.orders.get(orderId) ?? null;
  }

  async save(order) {
    this.orders.set(order.orderId, order);
    return order;
  }
}

service#

import { randomUUID } from "node:crypto";

export class OrderService {
  constructor(repository) {
    this.repository = repository;
  }

  async createOrder(input) {
    const order = {
      orderId: randomUUID(),
      userId: input.userId,
      amount: input.amount,
      status: "PENDING",
      createdAt: new Date().toISOString()
    };

    return this.repository.save(order);
  }
}

route#

import { Router } from "express";
import { z } from "zod";

export function createOrderRouter(orderService) {
  const router = Router();

  router.post("/orders", async (req, res, next) => {
    try {
      const input = z.object({
        userId: z.string().min(1),
        amount: z.number().positive()
      }).parse(req.body);

      const order = await orderService.createOrder(input);
      res.status(201).json(order);
    } catch (error) {
      next(error);
    }
  });

  return router;
}

13. Hands-on#

src/app.js#

import express from "express";
import { z } from "zod";
import { randomUUID } from "node:crypto";

const orders = new Map();

export function createApp() {
  const app = express();
  app.use(express.json());

  app.post("/orders", (req, res) => {
    const input = z.object({
      userId: z.string().min(1),
      amount: z.number().positive()
    }).parse(req.body);

    const order = {
      orderId: randomUUID(),
      userId: input.userId,
      amount: input.amount,
      status: "PENDING",
      createdAt: new Date().toISOString()
    };

    orders.set(order.orderId, order);
    res.status(201).json(order);
  });

  app.get("/orders/:orderId", (req, res) => {
    const order = orders.get(req.params.orderId);

    if (order === undefined) {
      res.status(404).json({
        error: {
          code: "ORDER_NOT_FOUND",
          message: "order not found"
        }
      });
      return;
    }

    res.json(order);
  });

  return app;
}

src/main.js#

import { createApp } from "./app.js";

const port = Number(process.env.PORT ?? 3000);
const app = createApp();

app.listen(port, () => {
  console.log(`order-api listening on port ${port}`);
});

run#

npm run dev
curl -s -X POST http://localhost:3000/orders \
  -H 'content-type: application/json' \
  -d '{"userId":"u-1001","amount":99.9}'

14. Production Checklist#

project:
    Node.js LTS
    ESM project
    package-lock.json committed
    npm ci in CI
    lint/test in CI

config:
    env config validated at startup
    secrets not committed
    production config injected by platform

code:
    external input validated
    async errors handled
    clients reused
    no sync IO in hot path
    no CPU-heavy work on event loop

runtime:
    structured logging
    request id / trace id
    health check endpoint
    graceful shutdown
    NODE_OPTIONS reviewed