PAS7 Studio
Ілюстрація стеку Bun.js, Elysia, Drizzle та PostgreSQL
Технології27 серп. 2026 р.·4 хв читання·Оновлено 27 серп. 2026 р.

Bun.js + Elysia + Drizzle: типобезпечний REST API з PostgreSQL

Практичний приклад API на Bun.js: Elysia для маршрутів, Drizzle ORM для типобезпечних SQL-запитів, PostgreSQL для даних і Zod для валідації.

TypeScript-розробникиFull-stack інженериАвтори API та мікросервісів

Elysia обробляє HTTP-маршрути, Drizzle описує таблиці TypeScript-кодом, а PostgreSQL зберігає дані. Bun запускає все одним процесом і не потребує окремого transpile-кроку.

Схема бази та типи відповіді виводяться з одного джерела.
Валідація відбувається на межі API, до виконання SQL-запиту.
Міграції залишаються явними й повторюваними для CI та production.
Xin

Чому саме цей стек

Кожна бібліотека має одну чітку роль, тому код легко замінювати або тестувати окремо.

Elysia

Bun-native HTTP-фреймворк із зручними route-маршрутами, middleware та схемами.

Drizzle

SQL-first ORM: таблиці описуються TypeScript-кодом, а запити залишаються близькими до SQL.

PostgreSQL

Надійне сховище для production, індексів, транзакцій і майбутнього росту домену.

Zod

Явно перевіряє вхідні дані й не дозволяє помилковому payload дійти до сервісного шару.

Запит проходить валідацію до SQL-рівня, а відповідь повертається клієнту з типізованим контрактом.

Скріншот секції stack

Крок 1: створюємо проєкт і підключаємо залежності

Bun сам встановить пакети й запускатиме TypeScript-файли без окремого bundler-а.

01

Ініціалізація

BASH
mkdir tasks-api && cd tasks-api
bun init
bun add elysia drizzle-orm postgres zod
bun add -d drizzle-kit
02

Змінні середовища

ENV
DATABASE_URL=postgres://app:app@localhost:5432/tasks
PORT=3000

Bun автоматично читає .env під час запуску. Секрети не потрібно вбудовувати у вихідний код.

03

Конфігурація міграцій

TS
// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  schema: "./src/db/schema.ts",
  out: "./drizzle",
  dialect: "postgresql",
  dbCredentials: { url: process.env.DATABASE_URL! },
});

Додайте scripts: "db:generate": "drizzle-kit generate" та "db:migrate": "drizzle-kit migrate".

Крок 2: описуємо таблицю та міграцію

Таблиця є джерелом типів для insert і select. Не створюйте паралельний ручний тип Task, якщо його можна вивести з Drizzle.

TS
// src/db/schema.ts
import { boolean, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";

export const tasks = pgTable("tasks", {
  id: uuid("id").defaultRandom().primaryKey(),
  title: text("title").notNull(),
  done: boolean("done").notNull().default(false),
  createdAt: timestamp("created_at", { withTimezone: true }).defaultNow().notNull(),
});

export type Task = typeof tasks.$inferSelect;
export type NewTask = typeof tasks.$inferInsert;

Запустіть bun run db:generate, а потім bun run db:migrate. Міграцію комітьте в репозиторій: production має застосовувати відомий SQL, а не генерувати його навмання.

Крок 3: додаємо типобезпечні маршрути Elysia

Elysia може перевіряти схеми на вході. Для невеликого прикладу використаємо Zod через t, але в реальному сервісі варто винести handler-и в окремий service layer.

TS
// src/index.ts
import { Elysia, t } from "elysia";
import { desc, eq } from "drizzle-orm";
import { z } from "zod";
import { db } from "./db/client";
import { tasks } from "./db/schema";

const createTask = z.object({ title: z.string().trim().min(1).max(120) });

const app = new Elysia()
  .get("/health", () => ({ status: "ok", runtime: "bun" }))
  .get("/tasks", async () =>
    db.select().from(tasks).orderBy(desc(tasks.createdAt)),
  )
  .post("/tasks", async ({ body, set }) => {
    const input = createTask.parse(body);
    const [task] = await db.insert(tasks).values(input).returning();
    set.status = 201;
    return task;
  }, { body: t.Object({ title: t.String({ minLength: 1, maxLength: 120 }) }) })
  .patch("/tasks/:id", async ({ params, body }) => {
    const [task] = await db
      .update(tasks)
      .set({ done: body.done })
      .where(eq(tasks.id, params.id))
      .returning();
    return task ?? new Response("Not found", { status: 404 });
  }, {
    params: t.Object({ id: t.String() }),
    body: t.Object({ done: t.Boolean() }),
  })
  .listen(Number(Bun.env.PORT ?? 3000));

console.log(`API running at http://localhost:${app.server?.port}`);

Повний database client і Docker Compose

З'єднання створюємо один раз на процес. Для локальної розробки Compose дає команді однакову версію PostgreSQL, а healthcheck не дозволяє міграціям стартувати раніше за базу.

TS
// src/db/client.ts
import postgres from "postgres";
import { drizzle } from "drizzle-orm/postgres-js";

const sql = postgres(Bun.env.DATABASE_URL!, {
  max: Number(Bun.env.DB_POOL_SIZE ?? 10),
  prepare: false,
});

export const db = drizzle(sql);

Для serverless-провайдера обирайте pooler або HTTP-драйвер, а для довгоживучого Bun-сервера звичайний pool із лімітом з'єднань зазвичай простіший.

YAML
# compose.yaml
services:
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: app
      POSTGRES_PASSWORD: app
      POSTGRES_DB: tasks
    ports: ["5432:5432"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d tasks"]
      interval: 2s
      timeout: 3s
      retries: 10

Транзакції, помилки та production-поради

Швидкий runtime не компенсує нечіткі межі даних. Ось три правила, які збережуть API передбачуваним.

Тестуємо handler без магії

Elysia дозволяє викликати застосунок через handle, тому smoke-тест не потребує відкритого порту. Для інтеграційного тесту підставте тестову базу через DATABASE_URL.

TS
// src/index.test.ts
import { describe, expect, test } from "bun:test";
import { app } from "./index";

describe("tasks API", () => {
  test("rejects an empty title", async () => {
    const response = await app.handle(
      new Request("http://localhost/tasks", {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ title: "  " }),
      }),
    );
    expect(response.status).toBe(422);
  });
});

Експортуйте app окремо від listen, щоб тест не запускав справжній listener. Запуск: bun test.

Перевірка локально та в CI

01

Запустіть PostgreSQL та API

BASH
docker run --name tasks-db -e POSTGRES_PASSWORD=app -e POSTGRES_USER=app -e POSTGRES_DB=tasks -p 5432:5432 -d postgres:16
bun run db:migrate
bun run src/index.ts
02

Створіть задачу

BASH
curl -X POST http://localhost:3000/tasks -H "content-type: application/json" -d '{"title":"Перевірити Bun API"}'
curl http://localhost:3000/tasks
03

Залиште короткий pipeline

BASH
bun install --frozen-lockfile
bun run db:migrate
bun test

Для тестів піднімайте окрему базу або використовуйте Testcontainers; не запускайте тести проти production PostgreSQL.

FAQ

Часті запитання

Чи можна використовувати Drizzle з Bun?

Так. Drizzle працює з Bun, а для PostgreSQL можна використовувати пакет `postgres` або сумісний драйвер. Перевіряйте версії драйвера у своєму deployment-середовищі.

Чи потрібен Node.js для Elysia?

Для запуску API на Bun — ні. Bun має власний runtime, package manager і TypeScript execution. Node.js може залишатися встановленим для інших проєктів.

Перевірено: 27 серп. 2026 р.Актуально для: Bun 1.3+Актуально для: Elysia 1.xАктуально для: Drizzle ORM 0.44+Актуально для: PostgreSQL 16+Перевірено з: Bun runtimeПеревірено з: ElysiaПеревірено з: drizzle-kitПеревірено з: PostgreSQL через Docker

Висновок

Опишіть задачу — перші 15 хвилин консультації безкоштовні.

Пов'язані статті

Скільки коштує розробка AI асистента у 2026: RAG чатбот, база знань, CRM, Telegram та підтримка
ai-assistants

Скільки коштує розробка AI асистента у 2026: RAG чатбот, база знань, CRM, Telegram та підтримка

Практичний гід для бізнесу: від чого залежить ціна розробки AI асистента у 2026 році, що входить у RAG чатбот, інтеграції з CRM, Telegram, guardrails, оцінювання, моніторинг і супровід.

AI для розробки лендінгів: де він реально прискорює запуск, а де псує конверсію
blogs

AI для розробки лендінгів: де він реально прискорює запуск, а де псує конверсію

Дослідження про використання AI у розробці лендінгів: v0, Webflow AI, Builder.io, Framer-подібні AI builders, генерація UX, copy, SEO, персоналізація, A/B тести, ризики шаблонності, безпеки, доступності та технічного боргу.

AI SEO / GEO у 2026: ваші наступні клієнти — не люди, а агенти
growth

AI SEO / GEO у 2026: ваші наступні клієнти — не люди, а агенти

Пошук зміщується від кліків до відповідей. Боти та AI-агенти сканують, цитують, рекомендують і дедалі частіше купують. Дізнайтесь, що таке AI SEO / GEO, чому класичного SEO вже недостатньо, і як PAS7 Studio допомагає брендам перемагати у «агентному» вебі.

Найпотужніший чіп від Apple? M5 Pro і M5 Max б'ють рекорди
blogs

Найпотужніший чіп від Apple? M5 Pro і M5 Max б'ють рекорди

Аналітичний розбір Apple M5 Pro і M5 Max станом на березень 2026 року. Пояснюємо, чому ці чіпи можна вважати найпотужнішими професійними ноутбучними SoC від Apple, як вони виглядають на тлі M4 Pro, M4 Max, M1 Pro, M1 Max і що показують у порівнянні з актуальними Intel та AMD.

Професійна розробка для вашого бізнесу

Створюємо сучасні веб-рішення та боти для бізнесу. Дізнайтеся, як ми можемо допомогти вам досягти цілей.