Node 启动慢,Express 路由散。TypeORM 写个 CRUD 三个文件。Java Spring 注解比代码还多。PHP 老项目维护,像在考古。想要启动快,类型好。还得跑得动高并发。数据库得原生,别给我加太多魔法。一套就够:
import { Elysia } from 'elysia'
import { Pool } from 'pg'
const pool = new Pool({
connectionString: 'postgres://app:app@localhost/app'
})
new Elysia()
.get('/users', async () => {
const { rows } = await pool.query('select id, name from users')
return rows
})
.listen(3000)
bun run app.ts 一下。10 行代码,一个接口搞定。启动 30 毫秒,类型端到端。SQL 直接写,没 ORM 那一层。这就是 Bun + Elysia + pg 的解法。
Bun 是答案。原生 TS,启动 30 毫秒。自带 test、bundler、package manager。一个二进制,替代一堆工具。
Elysia 是 Bun 上的 Web 框架。端到端类型推导。编辑器直接补全。比 Express 快 5 到 21 倍。比 NestJS 轻十倍。不挑数据库,你想用啥就用啥。
pg 是 Node 圈最稳的 PG 驱动。没有 ORM 那一层魔法。SQL 写啥就跑啥。出问题了,一眼能看清。
为啥不接 ORM?电商业务表多,关系复杂。ORM 给你自动 join,效率反而低。直接 SQL,心里踏实。
上代码。
一、最小可用服务
import { Elysia } from 'elysia'
import { Pool } from 'pg'
const pool = new Pool({
host: '127.0.0.1',
port: 5432,
user: 'app',
password: 'app',
database: 'app'
})
const app = new Elysia()
.get('/', () => '老铁你好')
.get('/users', async () => {
const { rows } = await pool.query(
'select id, name from users order by id desc'
)
return rows
})
.listen(3000)
console.log(`跑在 ${app.server!.url}`)
bun run app.ts 一下就起来。不用等 Tomcat。不用配 Nginx + PHP-FPM。不用 Spring 那一坨 XML。一个文件,就是一个服务。
二、POST 接口 + 参数校验
手写 SQL 太累?拼字符串又怕注入?Elysia 自带 validation。
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/users', async ({ body }) => {
const { rows } = await pool.query(
'insert into users(name) values($1) returning id, name',
[body.name]
)
return rows[0]
}, {
body: t.Object({
name: t.String({ minLength: 1, maxLength: 50 })
})
})
.listen(3000)
body 进来就是类型好的。不合法直接 422。if 判断全省了。类型就是文档,省心。
三、项目大了,路由怎么拆
老项目最怕啥?一个文件 3000 行。找个接口按 Ctrl+F 半天。
Elysia 用 use 拆模块。
// src/modules/user.ts
import { Elysia } from 'elysia'
export const userModule = new Elysia({ prefix: '/user' })
.get('/', () => 'user list')
.get('/:id', ({ params }) => `user id: ${params.id}`)
.post('/', ({ body }) => body)
// src/index.ts
import { Elysia } from 'elysia'
import { userModule } from './modules/user'
const app = new Elysia()
.use(userModule)
.listen(3000)
prefix 一加,路径自动拼。模块之间不打架。比 Spring 的 @RequestMapping 还省事。
四、连接池怎么管
Bun 起多实例。PG 连接要复用。pg.Pool 默认就是连接池。但参数得调一下。
const pool = new Pool({
host: process.env.PG_HOST,
port: Number(process.env.PG_PORT ?? 5432),
user: process.env.PG_USER,
password: process.env.PG_PASSWORD,
database: process.env.PG_DB,
max: 20,
idleTimeoutMillis: 30000
})
max 控并发。idleTimeoutMillis 释放空闲。K8s 里 pod 频繁重启。连接不能漏。Bun 退出 hook 会清。
五、事务怎么写
电商下单,最怕啥?钱扣了,货没发。事务没写好,赔钱又挨骂。
PG 原生 BEGIN / COMMIT。
import { Elysia, t } from 'elysia'
const app = new Elysia()
.post('/order', async ({ body }) => {
const client = await pool.connect()
try {
await client.query('BEGIN')
await client.query(
'update accounts set money = money - $1 where id = $2',
[body.amount, body.from]
)
await client.query(
'update accounts set money = money + $1 where id = $2',
[body.amount, body.to]
)
await client.query('COMMIT')
return { ok: true }
} catch (e) {
await client.query('ROLLBACK')
throw e
} finally {
client.release()
}
}, {
body: t.Object({
from: t.Number(),
to: t.Number(),
amount: t.Number({ minimum: 1 })
})
})
.listen(3000)
简单粗暴。没 ORM 那一层封装。出问题一眼能看清。比 MySQL 的事务稳。
六、监控怎么办
运维大哥天天问:QPS 多少?延迟多少?错误率涨没涨?
Elysia 自带生命周期钩子。
import { Elysia } from 'elysia'
const app = new Elysia()
.onRequest(({ request }) => {
console.log(`-> ${request.method} ${request.url}`)
})
.onAfterHandle(({ set }) => {
console.log(`<- ${set.status}`)
})
.onError(({ code, error }) => {
console.error('出事儿了:', error)
})
.get('/', () => 'ok')
要接 Prometheus 也简单。@elysiajs/prometheus 一行 .use() 就行。不用自己写中间件。
七、几个避坑点
写了一年,踩了不少坑。说几个常见的。
1. 千万别用字符串拼接 SQL:
// 错 ❌
await pool.query(
`select * from users where name = '${name}'`
)
// 对 ✅
await pool.query(
'select * from users where name = $1',
[name]
)
$1 是参数化。注入风险直接没了。
2. 时间用 timestamptz,别用 timestamp:
create table orders (
id serial primary key,
user_id int not null,
amount numeric(10, 2) not null,
created_at timestamptz not null default now()
);
timestamp 没时区。跨时区业务,必踩坑。timestamptz 才靠谱。
3. JSON 字段用 jsonb:
create table products (
id serial primary key,
name text not null,
attrs jsonb not null default '{}'::jsonb
);
jsonb 能建索引。json 不行。商品属性、配置项,用 jsonb。
4. 记得建索引:
create index idx_orders_user_id on orders(user_id);
create index idx_orders_created_at on orders(created_at desc);
没索引的 PG,慢得跟 MySQL 一样。索引建对了,快得飞起。
八、啥时候 pg 能替 redis
不少项目,redis 就是为了缓存。session 也是。限流也是。排队也是。pub/sub 也是。
但你真用了吗?装 redis 麻烦。运维要搭集群。内存贵。吃资源。
PG 自带这些功能。不用装第二个服务。省钱。省事。
下面几种场景,PG 完全能替。
1. 简单 KV 缓存
不用 redis 的 hash,不用 redis 的 list。就 KV 一把梭。
create table if not exists cache_kv (
key text primary key,
value jsonb not null,
expire_at timestamptz
);
写入:
await pool.query(
`insert into cache_kv(key, value, expire_at)
values($1, $2, $3)
on conflict (key) do update set
value = excluded.value,
expire_at = excluded.expire_at`,
[key, JSON.stringify(value), new Date(Date.now() + 60_000)]
)
读取(自动过滤过期):
const r = await pool.query(
`select value from cache_kv
where key = $1
and (expire_at is null or expire_at > now())`,
[key]
)
const value = r.rows[0] ? JSON.parse(r.rows[0].value) : null
60 秒过期,够用。电商商品列表、配置项,都这么干。
2. Session 存储
redis 存 session 太常见。PG 也能存。还多一份备份。
create table if not exists sessions (
id text primary key,
user_id int not null,
data jsonb not null,
expire_at timestamptz not null
);
写入:
await pool.query(
`insert into sessions(id, user_id, data, expire_at)
values($1, $2, $3, $4)
on conflict (id) do update set
data = excluded.data,
expire_at = excluded.expire_at`,
[token, userId, JSON.stringify(data), new Date(Date.now() + 86_400_000)]
)
读 session:
const { rows } = await pool.query(
`select user_id, data from sessions
where id = $1 and expire_at > now()`,
[token]
)
定时清理:
await pool.query(`delete from sessions where expire_at < now()`)
3. 分布式锁
秒杀、抢单,最怕并发。PG 自带 advisory lock。一行 SQL 一个锁。
// 加锁
await pool.query('select pg_advisory_lock($1)', [lockKey])
try {
// 业务逻辑
await doOrder()
} finally {
// 释放
await pool.query('select pg_advisory_unlock($1)', [lockKey])
}
或者非阻塞版:
const r = await pool.query(
'select pg_try_advisory_lock($1) as got',
[lockKey]
)
if (!r.rows[0].got) {
throw new Error('排队中')
}
比 redis 的 setnx 还简单。
4. Pub/Sub
实时通知、订单状态推送。不用装 redis。PG 的 LISTEN/NOTIFY 就够。
import { Client } from 'pg'
// 订阅
const sub = new Client({
host: '127.0.0.1',
port: 5432,
user: 'app',
password: 'app',
database: 'app'
})
await sub.connect()
await sub.query('LISTEN order_event')
sub.on('notification', (msg) => {
console.log('收到:', msg.payload)
})
发布:
await pool.query(
`select pg_notify($1, $2)`,
['order_event', JSON.stringify({ orderId: 123, status: 'paid' })]
)
小项目、内部通知,够用。千万级消息流不行。那老老实实上 redis 或 kafka。
5. 任务队列
电商订单要异步发货。PG 也能排队。
create table jobs (
id bigserial primary key,
type text not null,
payload jsonb not null,
status text not null default 'pending',
retry int not null default 0,
created_at timestamptz not null default now()
);
worker 抢任务:
const { rows } = await pool.query(`
update jobs
set status = 'running', locked_at = now()
where id = (
select id from jobs
where status = 'pending'
order by id
for update skip locked
limit 1
)
returning *
`)
if (rows[0]) {
await handle(rows[0])
await pool.query(`update jobs set status = 'done' where id = $1`, [rows[0].id])
}
SKIP LOCKED 是关键。多个 worker 不抢同一个任务。比 redis 的 brpop 简单。失败还能重试,数据不丢。
6. 限流
防刷、防爆。PG 也能做。
create table if not exists rate_limit (
key text primary key,
count int not null default 0,
window_start timestamptz not null default now()
);
const { rows } = await pool.query(`
insert into rate_limit(key, count, window_start)
values($1, 1, now())
on conflict (key) do update set
count = case
when rate_limit.window_start < now() - interval '1 minute'
then 1
else rate_limit.count + 1
end,
window_start = case
when rate_limit.window_start < now() - interval '1 minute'
then now()
else rate_limit.window_start
end
returning count
`, [ipKey])
if (rows[0].count > 100) {
throw new Error('请求太快')
}
简单粗暴。QPS 万级以内够用。
啥时候别替
PG 不是万能的。下面场景老老实实上 redis:
-
• 百万级 QPS 缓存
-
• 复杂数据结构(sorted set、bitmap)
-
• 大 key 大 value
-
• 强内存计算
-
• 全内存热数据
PG 优势在关系和事务。纯缓存场景,redis 更快。
但中小项目,流量没上百万。业务还要事务。数据还要持久。
PG 一把梭。省一个服务。省一份钱。省一份运维。
总结一下
Bunjs 启动快,原生 TS。Elysia 类型好,性能强。pg 是 Node 圈最稳的 PG 驱动。这三样加一起。就是 2026 的后端答案。
写电商写 OA 写 CRM。统统能扛。启动 30 毫秒,并发扛得住。类型端到端,BUG 少一半。
AI 写代码现在确实多。但框架选错了,AI 越写越乱。选对了,AI 帮你提速三倍。这套组合,省心。
要整就整这套。别的真不行。