Links#
https://nodejs.org/api/esm.html
https://nodejs.org/api/packages.html
https://nodejs.org/api/crypto.html
https://www.prisma.io/docs/orm
https://www.prisma.io/docs/orm/prisma-client/queries/crud
1. Goal#
目标:
用一个尽可能小的 order project 覆盖 Node.js / JavaScript 常用语法
使用 Prisma 作为 Node.js 常见 ORM 示例
每段代码都用注释标明用了什么语法,以及为什么这么用
you will see:
ESM import/export
object / array / destructuring / spread
class
async / await
Map
nullish coalescing
optional field handling
switch
error handling
Prisma schema
Prisma Client
2. Project Structure#
node-syntax-project
├── package.json
├── .env
├── prisma
│ └── schema.prisma
└── src
├── main.js
├── prisma.js
├── repository.js
└── service.js
3. Package / Build Files#
package.json#
{
"name": "node-syntax-project",
"version": "1.0.0",
"type": "module",
"private": true,
"scripts": {
"dev": "node src/main.js",
"prisma:generate": "prisma generate",
"prisma:migrate": "prisma migrate dev --name init"
},
"dependencies": {
"@prisma/client": "latest"
},
"devDependencies": {
"prisma": "latest"
}
}
.env#
DATABASE_URL="file:./dev.db"
prisma/schema.prisma#
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = env("DATABASE_URL")
}
model Order {
orderId String @id @default(uuid())
userId String
status String
createdAt DateTime @default(now())
paidAt DateTime?
items OrderItem[]
}
model OrderItem {
id Int @id @default(autoincrement())
orderId String
sku String
name String
quantity Int
amount Float
currency String
order Order @relation(fields: [orderId], references: [orderId], onDelete: Cascade)
}
4. Main Source Files#
src/prisma.js#
import { PrismaClient } from "@prisma/client";
// singleton: 一个进程复用一个 PrismaClient,避免频繁创建数据库连接
export const prisma = new PrismaClient();
src/repository.js#
export class PrismaOrderRepository {
// class + constructor: 把 PrismaClient 作为依赖注入,方便测试替换
constructor(db) {
this.db = db;
}
async findById(orderId) {
const record = await this.db.order.findUnique({
where: { orderId },
// ORM relation: include items,一次查出 order 和 order_items
include: { items: true }
});
// null check: Prisma 查不到时返回 null,service 层可以明确处理
if (record === null) {
return null;
}
// object literal + map: 把 ORM record 映射成业务对象
return {
orderId: record.orderId,
userId: record.userId,
status: record.status,
createdAt: record.createdAt,
// spread + conditional: paidAt 为空时不放进对象
...(record.paidAt === null ? {} : { paidAt: record.paidAt }),
items: record.items.map((item) => ({
sku: item.sku,
name: item.name,
quantity: item.quantity,
price: {
amount: item.amount,
currency: item.currency
}
}))
};
}
async save(order) {
await this.db.order.upsert({
where: { orderId: order.orderId },
update: {
status: order.status,
paidAt: order.paidAt,
items: {
deleteMany: {},
create: order.items.map((item) => ({
sku: item.sku,
name: item.name,
quantity: item.quantity,
amount: item.price.amount,
currency: item.price.currency
}))
}
},
create: {
orderId: order.orderId,
userId: order.userId,
status: order.status,
createdAt: order.createdAt,
paidAt: order.paidAt,
items: {
create: order.items.map((item) => ({
sku: item.sku,
name: item.name,
quantity: item.quantity,
amount: item.price.amount,
currency: item.price.currency
}))
}
}
});
return order;
}
async list() {
const records = await this.db.order.findMany({
include: { items: true },
orderBy: { createdAt: "desc" }
});
// Promise.all: 并行等待多个 async mapping 结果
return Promise.all(records.map((record) => this.findById(record.orderId)))
// filter(Boolean): 去掉 null 结果,保持返回 order list
.then((orders) => orders.filter(Boolean));
}
}
src/service.js#
import { randomUUID } from "node:crypto";
export class OrderService {
constructor(repository) {
this.repository = repository;
}
async create(input) {
const order = {
orderId: randomUUID(),
userId: input.userId,
status: "PENDING",
items: input.items,
createdAt: new Date()
};
await this.repository.save(order);
this.handleAudit({ type: "ORDER_CREATED", orderId: order.orderId, at: new Date() });
return order;
}
async pay(orderId) {
const order = await this.repository.findById(orderId);
if (order === null) {
return { ok: false, error: "ORDER_NOT_FOUND" };
}
if (order.status !== "PENDING") {
return { ok: false, error: "INVALID_STATUS" };
}
// spread: 基于旧对象创建新状态,不直接修改原对象
const paidOrder = {
...order,
status: "PAID",
paidAt: new Date()
};
await this.repository.save(paidOrder);
this.handleAudit({ type: "ORDER_PAID", orderId, transactionId: randomUUID(), at: new Date() });
return { ok: true, value: paidOrder };
}
async summarize([limit = 20] = []) {
const orders = await this.repository.list();
return orders.slice(0, limit).map((order) => ({
orderId: order.orderId,
userId: order.userId,
status: order.status,
// reduce: 把多个 item 金额聚合成订单总额
totalAmount: order.items.reduce(
(sum, item) => sum + item.quantity * item.price.amount,
0
)
}));
}
handleAudit(event) {
// switch: 根据 event.type 明确处理不同事件结构
switch (event.type) {
case "ORDER_CREATED":
console.log(`created ${event.orderId}`);
return;
case "ORDER_PAID":
console.log(`paid ${event.orderId} by ${event.transactionId}`);
return;
default:
throw new Error(`unknown event type: ${event.type}`);
}
}
}
src/main.js#
import { prisma } from "./prisma.js";
import { PrismaOrderRepository } from "./repository.js";
import { OrderService } from "./service.js";
const repository = new PrismaOrderRepository(prisma);
const service = new OrderService(repository);
const order = await service.create({
userId: "u-1001",
items: [
{
sku: "sku-1001",
name: "Keyboard",
quantity: 1,
price: {
amount: 99.9,
currency: "USD"
}
}
]
});
const paid = await service.pay(order.orderId);
// discriminated result pattern: ok=true 时读 value,ok=false 时读 error
if (paid.ok) {
console.log("paid order", paid.value);
} else {
console.log("pay failed", paid.error);
}
console.log(await service.summarize([10]));
await prisma.$disconnect();
5. Run#
npm install
npm run prisma:migrate
npm run dev
6. Reading Order#
read files in this order:
1. prisma/schema.prisma
2. prisma.js
3. repository.js
4. service.js
5. main.js
理解重点:
Prisma schema defines database model
repository maps ORM records to business objects
service owns business logic
main wires everything together