NodeJS
本文缩写(英文全称与中文含义)
| 缩写 | 英文全称/名称 | 中文含义 |
|---|---|---|
| API | Application Programming Interface | 应用程序编程接口 |
| BFF | Backend For Frontend | 为特定前端提供的后端层 |
| CI | Continuous Integration | 持续集成 |
| CLI | Command-Line Interface | 命令行接口 |
| CPU | Central Processing Unit | 中央处理器 |
| ESM | ECMAScript Modules | JavaScript 标准模块系统 |
| HTTP | Hypertext Transfer Protocol | 超文本传输协议 |
| IO | Input/Output | 输入/输出 |
| JSON | JavaScript Object Notation | 一种结构化数据格式 |
| LTS | Long-Term Support | 长期支持版本 |
| OS | Operating System | 操作系统 |
从零打包:Hello World:npm 与 Docker。
Links#
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-started1. 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 productionmental model:
JavaScript code
-> Node.js runtime
-> event loop handles async IO
-> libuv handles many OS async operations
-> npm manages packages2. Environment Setup#
versions#
recommended:
Node.js LTS
npm bundled with Node.js
check:
node -v
npm -vcreate project#
mkdir order-api
cd order-api
npm init -y
npm install express dotenv zod pino
npm install -D eslint @eslint/js globals vitestdependencies:
express:
HTTP server framework
dotenv:
local .env loading
zod:
runtime validation
pino:
structured JSON logger3. 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.jsstructure 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/controller4. 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 CIimport / 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 extension5. Syntax You Need Most#
variables#
const orderId = "o-1001";
let status = "PENDING";
status = "PAID";异步错误要进入统一错误处理;不要把原始 stack 或数据库错误直接返回客户端。
rule:
use const by default
use let only when reassignment is needed
avoid varobject / 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 modules6. 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 client7. 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 testwhat to test:
service business logic
validation
repository
HTTP routes
error mapping8. Lint#
install#
npm install -D eslint @eslint/jseslint.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:fixlint vs test:
lint:
catches code quality and common mistakes
test:
checks behavior9. 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:8080typed-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 secrets11. Build / Run / Debug#
dev#
npm run devtest#
npm testlint#
npm run lintproduction#
npm run startdebug#
node --inspect-brk src/main.jsproduction:
run with NODE_ENV=production
use process manager or container orchestrator
configure graceful shutdown12. 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 devcurl -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