Drizzle ORM — первая программа
Drizzle ORM — первая программа
Drizzle ORM — компактный ORM для TypeScript:
- схема таблиц пишется на TypeScript;
- запросы близки к SQL;
- миграции через drizzle-kit.
Отдельного DSL-файла вроде schema.prisma у Prisma нет — всё живёт в коде.
Подходит, если хотите полный контроль над SQL и минимальный runtime overhead.
| Шаг | Действие | Результат |
|---|---|---|
| 1 | npm init, зависимости | Каркас проекта |
| 2 | src/schema.ts | Описание таблицы |
| 3 | drizzle-kit generate | SQL-миграция |
| 4 | drizzle-kit migrate | Файл dev.db |
| 5 | CRUD в seed.ts | Проверка запросов |
| 6 | Express REST | API "Заметки" |
| 7 | PostgreSQL | Переход на production-СУБД |
| Материал | Зачем |
|---|---|
| Prisma ORM — первая программа | альтернатива с schema.prisma |
| Первая программа на Node.js | REST, curl |
| Express — middleware | маршруты |
| NestJS | сервис-слой |
| npm | scripts, devDependencies |
| TypeScript | схема и запросы |
Навигация по блоку Node.js
- REST: Первая программа на Node.js
- Prisma: Prisma ORM — первая программа
- Вы здесь: Drizzle ORM — первая программа
Если вы уже comfortable с SQL и хотите видеть структуру запросов в TypeScript — Drizzle даёт SQL-подобный синтаксис без отдельного языка схемы.
Архитектура Drizzle
| Пакет | Назначение |
|---|---|
drizzle-orm | Query builder в runtime |
drizzle-kit | CLI миграций (devDependency) |
better-sqlite3 | Драйвер SQLite для Node |
tsx | Запуск TypeScript без сборки |
Шаг 1 — проект с нуля
mkdir notes-drizzle
cd notes-drizzle
npm init -y
package.json:
{
"name": "notes-drizzle",
"type": "module",
"scripts": {
"dev": "tsx watch src/server.ts",
"seed": "tsx src/seed.ts",
"db:generate": "drizzle-kit generate",
"db:migrate": "drizzle-kit migrate"
}
}
Установка:
npm i drizzle-orm better-sqlite3 express
npm i -D drizzle-kit tsx typescript @types/better-sqlite3 @types/express @types/node
npx tsc --init
Шаг 2 — конфиг drizzle-kit
drizzle.config.ts:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({
schema: './src/schema.ts',
out: './drizzle',
dialect: 'sqlite',
dbCredentials: { url: './dev.db' },
});
Разбор:
schema— путь к TypeScript-описанию таблиц.out— каталог SQL-миграций.dialect— тип СУБД (sqlite,postgresql,mysql).
Шаг 3 — схема на TypeScript
src/schema.ts:
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
export const notes = sqliteTable('notes', {
id: integer('id').primaryKey({ autoIncrement: true }),
text: text('text').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' })
.$defaultFn(() => new Date()),
});
Разбор:
| Метод | Смысл |
|---|---|
sqliteTable('notes', {...}) | Имя таблицы в БД |
integer(...).primaryKey({ autoIncrement: true }) | AUTOINCREMENT PK |
text('text').notNull() | NOT NULL колонка |
mode: 'timestamp' | JavaScript Date ↔ integer в SQLite |
Экспорт notes используется и в миграциях, и в запросах — один источник правды.
Шаг 4 — миграции
npm run db:generate
npm run db:migrate
После generate в drizzle/ появится SQL-файл с CREATE TABLE. migrate применяет его к dev.db.
Сначала меняете schema.ts, затем generate, затем migrate. Пропуск migrate даёт ошибку "no such table".
Шаг 5 — подключение и seed
src/db.ts:
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import * as schema from './schema.js';
const sqlite = new Database('dev.db');
export const db = drizzle(sqlite, { schema });
src/seed.ts:
import { db } from './db.js';
import { notes } from './schema.js';
import { desc } from 'drizzle-orm';
async function main() {
await db.delete(notes);
await db.insert(notes).values([
{ text: 'Первая заметка Drizzle' },
{ text: 'Вторая заметка' },
]);
const rows = await db.select().from(notes).orderBy(desc(notes.id));
console.log(rows);
}
main();
npm run seed
Шаг 6 — CRUD-операции
| Задача | Drizzle |
|---|---|
| SELECT все | db.select().from(notes) |
| WHERE id | db.select().from(notes).where(eq(notes.id, id)) |
| INSERT | db.insert(notes).values({ text }) |
| INSERT + return | .returning() |
| DELETE | db.delete(notes).where(eq(notes.id, id)) |
| UPDATE | db.update(notes).set({ text }).where(eq(notes.id, id)) |
Пример src/queries-demo.ts:
import { db } from './db.js';
import { notes } from './schema.js';
import { eq } from 'drizzle-orm';
const [row] = await db
.insert(notes)
.values({ text: 'Demo' })
.returning();
await db
.update(notes)
.set({ text: 'Updated' })
.where(eq(notes.id, row.id));
await db.delete(notes).where(eq(notes.id, row.id));
.returning() возвращает созданную строку — удобно для REST 201 Created.
Шаг 7 — Express REST API (полный walkthrough)
src/server.ts:
import express from 'express';
import { db } from './db.js';
import { notes } from './schema.js';
import { desc, eq } from 'drizzle-orm';
const app = express();
app.use(express.json());
app.get('/health', (_req, res) => {
res.json({ status: 'ok' });
});
app.get('/notes', async (_req, res) => {
const rows = await db.select().from(notes).orderBy(desc(notes.id));
res.json(rows);
});
app.get('/notes/:id', async (req, res) => {
const id = Number(req.params.id);
const [row] = await db.select().from(notes).where(eq(notes.id, id));
if (!row) return res.status(404).json({ error: 'not found' });
res.json(row);
});
app.post('/notes', async (req, res) => {
const text = String(req.body?.text ?? '').trim();
if (!text) return res.status(400).json({ error: 'text required' });
const [row] = await db.insert(notes).values({ text }).returning();
res.status(201).json(row);
});
app.delete('/notes/:id', async (req, res) => {
const id = Number(req.params.id);
const deleted = await db.delete(notes).where(eq(notes.id, id)).returning();
if (deleted.length === 0) return res.status(404).json({ error: 'not found' });
res.status(204).end();
});
const PORT = process.env.PORT ?? 3000;
app.listen(PORT, () => console.log(`http://localhost:${PORT}`));
Проверка (как в 262):
npm run dev
curl http://127.0.0.1:3000/notes
curl -X POST http://127.0.0.1:3000/notes \
-H "Content-Type: application/json" \
-d '{"text":"Drizzle работает"}'
.returning() поддерживается в SQLite и PostgreSQL; для MySQL проверьте версию СУБД.
Шаг 8 — NestJS + Drizzle
src/database/database.module.ts:
import { Module, Global } from '@nestjs/common';
import { db } from '../db.js';
export const DRIZZLE = Symbol('DRIZZLE');
@Global()
@Module({
providers: [{ provide: DRIZZLE, useValue: db }],
exports: [DRIZZLE],
})
export class DatabaseModule {}
В сервисе:
import { Inject, Injectable } from '@nestjs/common';
import { DRIZZLE } from '../database/database.module';
import { notes } from '../schema.js';
import { desc } from 'drizzle-orm';
@Injectable()
export class NotesService {
constructor(@Inject(DRIZZLE) private readonly db: typeof import('./db.js').db) {}
findAll() {
return this.db.select().from(notes).orderBy(desc(notes.id));
}
}
Подробнее о NestJS — 269.
PostgreSQL вместо SQLite
npm i drizzle-orm postgres
npm i -D drizzle-kit
src/schema.ts — pgTable из drizzle-orm/pg-core:
import { pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
export const notes = pgTable('notes', {
id: serial('id').primaryKey(),
text: text('text').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
drizzle.config.ts:
export default defineConfig({
schema: './src/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: { url: process.env.DATABASE_URL! },
});
Подключение:
import postgres from 'postgres';
import { drizzle } from 'drizzle-orm/postgres-js';
const client = postgres(process.env.DATABASE_URL!);
export const db = drizzle(client, { schema });
Drizzle и Prisma — когда что выбирать
| Выбирайте Drizzle | Выбирайте Prisma |
|---|---|
| SQL-подобный стиль запросов | Единый schema.prisma и Studio |
| Минимум магии, tree-shaking | Богатые relation API |
| Edge/serverless (drizzle + libsql) | Команда уже на Prisma |
| Схема рядом с кодом | Визуальный Studio из коробки |
Обе ORM сочетаются с NestJS через сервис-слой.
Упражнения
- Добавьте колонку
done(integer 0/1) и эндпоинтPATCH /notes/:id/toggle. - Напишите join двух таблиц
notesиcategoriesчерезleftJoin. - Оберните insert+update в
db.transaction(). - Сравните тот же CRUD на Prisma — запишите отличия в комментарии.
- Подключите фронтенд Svelte через прокси Vite.
Частые ошибки и troubleshooting
| Симптом | Причина | Решение |
|---|---|---|
no such table: notes | Миграция не применена | npm run db:migrate |
Тип Date странный в SQLite | Без mode: 'timestamp' | Укажите mode в schema |
returning() пустой | Dialect без поддержки | Проверьте SQLite/PG версию |
Cannot find module './schema.js' | ESM paths | "type": "module" и .js в import |
| Дубликаты при seed | Нет delete перед insert | await db.delete(notes) |
drizzle-kit not found | devDependency | npm i -D drizzle-kit |
| Locked SQLite DB | Два процесса | Один npm run dev |
FAQ
Drizzle заменяет SQL?
Нет. Drizzle генерирует SQL под капотом. Сложные отчёты иногда пишут через db.execute(sql...).
Нужен ли отдельный schema-файл?
Схема — обычный TypeScript. Это плюс для тех, кто хочет всё в репозитории без DSL.
Есть ли GUI как Prisma Studio?
Официального Studio нет; используйте DB Browser for SQLite, TablePlus или pgAdmin.
Drizzle в serverless?
Да, с @libsql/client и Turso — популярная связка для edge.
Production и деплой
Миграции в CI
npm run db:migrate
npm run build
node dist/server.js
Храните SQL-миграции в git. На production — drizzle-kit migrate, не generate.
Connection pooling
Для PostgreSQL используйте postgres.js с лимитом соединений или PgBouncer. SQLite на production — редко; для нагрузки — PostgreSQL.
Переменные окружения
DATABASE_URL=postgresql://user:pass@host:5432/db
PORT=8080
Коммитьте миграции, не dev.db. Бэкапы PostgreSQL — через провайдера или pg_dump.
Шаг 9 — relations в Drizzle
src/schema.ts с тегами:
import { sqliteTable, integer, text, primaryKey } from 'drizzle-orm/sqlite-core';
import { relations } from 'drizzle-orm';
export const notes = sqliteTable('notes', {
id: integer('id').primaryKey({ autoIncrement: true }),
text: text('text').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
});
export const tags = sqliteTable('tags', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull().unique(),
});
export const notesToTags = sqliteTable(
'notes_to_tags',
{
noteId: integer('note_id').notNull().references(() => notes.id, { onDelete: 'cascade' }),
tagId: integer('tag_id').notNull().references(() => tags.id, { onDelete: 'cascade' }),
},
(t) => [primaryKey({ columns: [t.noteId, t.tagId] })],
);
export const notesRelations = relations(notes, ({ many }) => ({
notesToTags: many(notesToTags),
}));
После изменений: npm run db:generate && npm run db:migrate.
Join-запрос:
import { eq } from 'drizzle-orm';
const rows = await db
.select({
noteId: notes.id,
text: notes.text,
tagName: tags.name,
})
.from(notes)
.leftJoin(notesToTags, eq(notes.id, notesToTags.noteId))
.leftJoin(tags, eq(notesToTags.tagId, tags.id));
Шаг 10 — транзакции
await db.transaction(async (tx) => {
const [note] = await tx.insert(notes).values({ text: 'В транзакции' }).returning();
await tx.insert(tags).values({ name: `tag-for-${note.id}` });
});
При ошибке во втором insert первый откатится.
Шаг 11 — prepared statements и производительность
better-sqlite3 синхронный; для PostgreSQL используйте postgres.js:
import { drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';
const client = postgres(process.env.DATABASE_URL!, { max: 10 });
export const db = drizzle(client, { schema });
Параметр max — размер пула соединений.
Шаг 12 — drizzle-kit studio (просмотр данных)
npx drizzle-kit studio
Откроется UI для просмотра таблиц — аналог Prisma Studio (2691).
Полный Express с error handler
import express from 'express';
import { db } from './db.js';
import { notes } from './schema.js';
import { desc, eq } from 'drizzle-orm';
const app = express();
app.use(express.json());
app.get('/notes', async (_req, res, next) => {
try {
const rows = await db.select().from(notes).orderBy(desc(notes.id));
res.json(rows);
} catch (e) {
next(e);
}
});
app.use((err: unknown, _req: express.Request, res: express.Response, _next: express.NextFunction) => {
console.error(err);
res.status(500).json({ error: 'internal' });
});
const server = app.listen(3000);
process.on('SIGINT', () => {
server.close();
process.exit(0);
});
Сравнение запросов Prisma и Drizzle
| Задача | Prisma | Drizzle |
|---|---|---|
| Все заметки | prisma.note.findMany() | db.select().from(notes) |
| WHERE id | findUnique({ where: { id } }) | .where(eq(notes.id, id)) |
| INSERT | create({ data }) | insert(notes).values({}) |
| JOIN | include: { tags: true } | .leftJoin(...) |
| Raw SQL | $queryRaw | db.execute(sql...) |
Docker и PostgreSQL
docker-compose.yml — тот же паттерн, что в Prisma. Обновите drizzle.config.ts:
export default defineConfig({
schema: './src/schema.ts',
out: './drizzle',
dialect: 'postgresql',
dbCredentials: { url: process.env.DATABASE_URL! },
});
Entrypoint скрипт:
npm run db:migrate && npm run dev
Расширенный troubleshooting
| Симптом | Детали |
|---|---|
SQLITE_BUSY | Два writer — один процесс или PG |
| Migration drift | drizzle-kit push только dev |
| Type inference сломана | typeof notes.$inferSelect |
| Import schema circular | Вынесите relations отдельно |
Тип inferred row:
type Note = typeof notes.$inferSelect;
Дополнительные упражнения
- Пагинация через
.limit(10).offset(20). - Поиск
LIKEчерезlike(notes.text, '%молоко%'). - Unit-тест с in-memory
libsql(Drizzle + Turso). - Экспорт schema в SQL через
drizzle-kit export. - Интеграция с NestJS Guards — проверка JWT перед CRUD.
Шаг 13 — пагинация
app.get('/notes', async (req, res) => {
const page = Number(req.query.page) || 1;
const limit = Number(req.query.limit) || 20;
const offset = (page - 1) * limit;
const rows = await db
.select()
.from(notes)
.orderBy(desc(notes.id))
.limit(limit)
.offset(offset);
res.json({ items: rows, page, limit });
});
Шаг 14 — индексы в schema
import { index } from 'drizzle-orm/sqlite-core';
export const notes = sqliteTable(
'notes',
{
id: integer('id').primaryKey({ autoIncrement: true }),
text: text('text').notNull(),
createdAt: integer('created_at', { mode: 'timestamp' }).$defaultFn(() => new Date()),
},
(table) => [index('notes_text_idx').on(table.text)],
);
После generate + migrate поиск по text ускорится на больших таблицах.
Шаг 15 — тесты с vitest
import { describe, it, expect, beforeEach } from 'vitest';
import Database from 'better-sqlite3';
import { drizzle } from 'drizzle-orm/better-sqlite3';
import { notes } from './schema';
describe('notes table', () => {
let db: ReturnType<typeof drizzle>;
beforeEach(() => {
const sqlite = new Database(':memory:');
db = drizzle(sqlite);
// apply migration SQL to memory db
});
it('inserts row', async () => {
await db.insert(notes).values({ text: 'test' });
const rows = await db.select().from(notes);
expect(rows).toHaveLength(1);
});
});
In-memory SQLite — быстрые тесты без файла dev.db.
Миграция с Prisma на Drizzle (концептуально)
| Prisma | Drizzle |
|---|---|
schema.prisma | schema.ts |
prisma migrate | drizzle-kit generate/migrate |
PrismaClient | drizzle(db) |
| Studio | drizzle-kit studio |
Данные сохраняются в SQL — меняется только слой доступа. Подробное сравнение — 2691.
Production checklist
| Пункт | Действие |
|---|---|
| Миграции в git | drizzle/*.sql |
| CI migrate | drizzle-kit migrate |
| Pool size | Настройка драйвера |
| Индексы | .unique(), custom indexes |
| Логи SQL | logger: true в drizzle config (dev only) |
Второй проход — расширенный практикум (Drizzle ORM)
Серия мини-туториалов
Туториал 1 — Relations
Команда или API: relations() helper.
Детали: db.query.notes.findMany({ with: { tags: true } }).
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Relations |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 2 — Transactions
Команда или API: db.transaction.
Детали: async tx => tx.insert.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Transactions |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 3 — Prepared statements
Команда или API: placeholder.
Детали: performance repeated queries.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Prepared statements |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 4 — Views
Команда или API: pgView.
Детали: materialized reporting view.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Views |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 5 — Indexes
Команда или API: index on text.
Детали: migration add index.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Indexes |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 6 — Zod schema
Команда или API: drizzle-zod.
Детали: validate insert payload.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Zod schema |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 7 — D1 SQLite
Команда или API: drizzle-orm/d1.
Детали: Cloudflare Workers.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить D1 SQLite |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 8 — Turso libsql
Команда или API: @libsql/client.
Детали: edge database.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить Turso libsql |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 9 — PlanetScale
Команда или API: mysql dialect.
Детали: serverless MySQL.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить PlanetScale |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Туториал 10 — SQL template
Команда или API: sql...``.
Детали: complex joins raw escape hatch.
// пример шага
console.log('ok');
| Шаг | Проверка |
|---|---|
| 1 | Выполнить SQL template |
| 2 | Перезапустить dev-сервер |
| 3 | Убедиться в отсутствии ошибок в консоли |
Расширенные упражнения (второй проход)
- Column done integer toggle endpoint.
Подсказка к упражнению 13: Начните с минимального изменения, затем добавьте тест. Тема: Column.
- leftJoin categories table.
Подсказка к упражнению 14: Начните с минимального изменения, затем добавьте тест. Тема: leftJoin.
- db.transaction insert two notes.
Подсказка к упражнению 15: Начните с минимального изменения, затем добавьте тест. Тема: db.transaction.
- Compare same CRUD on Prisma doc.
Подсказка к упражнению 16: Начните с минимального изменения, затем добавьте тест. Тема: Compare.
- drizzle-kit studio alternative TablePlus export.
Подсказка к упражнению 17: Начните с минимального изменения, затем добавьте тест. Тема: drizzle-kit.
- Partial index migration generate.
Подсказка к упражнению 18: Начните с минимального изменения, затем добавьте тест. Тема: Partial.
- NestJS DRIZZLE provider symbol pattern.
Подсказка к упражнению 19: Начните с минимального изменения, затем добавьте тест. Тема: NestJS.
- Seed script with onConflictDoNothing.
Подсказка к упражнению 20: Начните с минимального изменения, затем добавьте тест. Тема: Seed.
- PostgreSQL switch from SQLite schema.
Подсказка к упражнению 21: Начните с минимального изменения, затем добавьте тест. Тема: PostgreSQL.
- Integration test with test.db file cleanup.
Подсказка к упражнению 22: Начните с минимального изменения, затем добавьте тест. Тема: Integration.
Расширенный FAQ (второй проход)
Drizzle Kit push?
drizzle-kit push dev only quick sync.
Type inference?
typeof notes.$inferSelect for row type.
Multiple schemas PG?
pgSchema('auth') for multi-tenant.
Better-sqlite3 sync?
Sync driver — только dev scripts.
Migrations squash?
Squash manually in git history carefully.
RLS PostgreSQL?
Policies outside Drizzle, raw SQL migration.
Batch insert?
insert values array chunk 500 rows.
Upsert?
onConflictDoUpdate target notes.id.
Logging queries?
logger: true in drizzle config dev.
Drizzle + Nest lifecycle?
OnModuleDestroy close sqlite.
Production — дополнительные рекомендации
| # | Практика | Зачем |
|---|---|---|
| 1 | Commit | Commit drizzle/ SQL migrations |
| 2 | Never | Never commit dev.db |
| 3 | postgres.js | postgres.js max connections tuned |
| 4 | CI | CI migrate before deploy |
| 5 | Separate | Separate read/write URLs if replicas |
| 6 | Monitor | Monitor migration lock timeouts |
| 7 | Use | Use env DATABASE_URL only |
| 8 | Test | Test migrations on staging clone |
Troubleshooting — расширенная таблица
| Симптом | Вероятная причина | Действие |
|---|---|---|
| Сборка падает без текста | Кэш или версия Node | Очистить node_modules, lock-файл, переустановить |
| Тесты flaky | Порядок или timing | Изолировать example, убрать sleep, добавить wait matchers |
| Production 502 | Process не слушает PORT | Проверить env PORT и health endpoint |
| Данные пропали после deploy | In-memory store или migrate | Подключить БД, migrate deploy |
| CORS в браузере | Прямой URL API | Proxy dev или enableCors origin |
| Медленный первый запрос | Cold start DB pool | Warmup health check после deploy |
| Ошибка подписи iOS | Certificate expired | Renew в Developer portal, download profiles |
| Turbo frame blank | Id mismatch | Сверить turbo-frame id в request и response |
| Prisma client outdated | Schema changed | npx prisma generate после migrate |
| Vite blank prod | Неверный base path | Проверить base и URL деплоя |
Пошаговый walkthrough — контрольный список
День 1
- Шаг 1 дня 1: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 1: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 1: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 1: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 1: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 2
- Шаг 1 дня 2: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 2: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 2: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 2: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 2: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 3
- Шаг 1 дня 3: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 3: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 3: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 3: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 3: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 4
- Шаг 1 дня 4: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 4: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 4: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 4: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 4: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 5
- Шаг 1 дня 5: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 5: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 5: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 5: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 5: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 6
- Шаг 1 дня 6: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 6: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 6: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 6: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 6: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
День 7
- Шаг 1 дня 7: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 2 дня 7: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 3 дня 7: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 4 дня 7: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
- Шаг 5 дня 7: закрепить часть стека Drizzle ORM. Запишите результат в README проекта.
# checkpoint
npm test || bundle exec rspec || echo manual check
Чек-лист самопроверки перед сдачей практикума
-
Проект создаётся с нуля по статье без пропусков шагов
-
CRUD или эквивалентный сценарий работает end-to-end
-
Есть обработка ошибок валидации или 404
-
Данные переживают перезапуск там, где это требуется темой
-
Написан минимум один автоматический тест или system check
-
Production-секция прочитана и применена к деплою или Docker
-
FAQ просмотрен — типичные ошибки воспроизведены и исправлены
-
Связанные материалы открыты для следующего шага обучения
Связанные материалы
| Тема | Материал |
|---|---|
| Prisma | Prisma ORM — первая программа |
| Node REST | Первая программа на Node.js |
| Express | Express — middleware, маршруты и ошибки |
| NestJS | Первая программа на NestJS |
| npm | npm — команды, зависимости и lock-файлы |
| SQL | SQL — о разделе |
| TypeScript | Первая программа на TypeScript |