7. Test, build, and deploy
1. Add repeatable showcase data
The CRUD pages work with manual data, but a seed makes a fresh checkout immediately recognizable.
Create src/db/seed.ts:
import { db } from './index'
import { categories, comments, posts, products, tags, users } from './schema'
import { postTaggings } from './schema/post-taggings'
const ids = {
category: crypto.randomUUID(),
product: crypto.randomUUID(),
author: crypto.randomUUID(),
tag: crypto.randomUUID(),
post: crypto.randomUUID(),
rootComment: crypto.randomUUID(),
reply: crypto.randomUUID(),
}
await db.insert(categories).values({ id: ids.category, name: 'Hardware' })
await db.insert(products).values({
name: 'Mechanical Keyboard',
id: ids.product,
price: '129.99',
active: true,
categoryId: ids.category,
})
await db.insert(users).values({
id: ids.author,
name: 'Bunway Author',
email: `author-${crypto.randomUUID()}@example.test`,
bio: 'Writes about building direct, Bun-native applications.',
})
await db.insert(tags).values({ id: ids.tag, name: 'Bun' })
await db.insert(posts).values({
id: ids.post,
title: 'Building with Bunway',
slug: `building-with-bunway-${crypto.randomUUID()}`,
excerpt: 'A generated application remains ordinary Bun, Elysia, Drizzle, and SvelteKit.',
body: 'Bunway supplies conventions and readable source code without hiding the underlying stack.',
userId: ids.author,
published: true,
publishedAt: new Date().toISOString(),
})
await db.insert(postTaggings).values({
tagId: ids.tag,
taggableType: 'Post',
taggableId: ids.post,
})
await db.insert(comments).values({
id: ids.rootComment,
body: 'The generated source is easy to follow.',
postId: ids.post,
userId: ids.author,
approved: true,
})
await db.insert(comments).values({
body: 'And nested discussions are still ordinary relational data.',
id: ids.reply,
postId: ids.post,
userId: ids.author,
parentId: ids.rootComment,
approved: true,
})
console.log('Showcase seed complete')
Add this script to the root package.json scripts object:
"db:seed": "bun src/db/seed.ts"
Run it once against an empty database:
bun run db:seed
The values are intentionally unique where required, but this is a fresh-database seed rather than an idempotent production task. Do not run it repeatedly against the same database.
2. Run automated verification
bun test
bun run typecheck
bun run build
bunway routes
All four commands must exit with status 0. bunway routes must include /blog,
/realtime/showcase/*, /examples/audit/*, /examples/messaging/*, Auth, Storage, and every generated
CRUD route.
3. Run the browser verification
Exercise CRUD, upload/download, same-process progress, WebSocket chat, password sign-in, TOTP, an Audit query, immediate console delivery, and queued console delivery. OAuth requires real credentials.
On PostgreSQL, also exercise a queued Job, queued Mail/SMS, and bunway worker. On MySQL and SQLite,
skip only those durable-queue checks.
Use this exact checklist:
/categoriesand/products: create/edit/delete and upload a Product image./users,/tags,/posts,/comments: confirm relationship pickers and generated detail pages./blog: confirm the seeded Post, Tag, root Comment, and nested reply render./realtime: use every button, then verify chat in two browser windows./register,/login,/account/security: register, sign in, and configure TOTP./examples/audit: record a normal event and the secret-redaction example./examples/messaging: send Mail/SMS now and inspect the API console and Audit cards; on PostgreSQL, also send later with a worker running.
4. Deploy
For production, apply migrations once, supervise the app (and the PostgreSQL worker, when used) with systemd, and proxy through Nginx with WebSocket upgrades and buffering disabled for SSE. Follow Deploy to a VPS and the production checklist.
What you built
- Elysia + Drizzle resources and an Eden/SvelteKit UI
- relationships and local/S3-compatible attachments
- SSE progress and WebSocket communication, plus PostgreSQL Jobs when PostgreSQL is selected
- Better Auth password identity, OAuth integration points, TOTP, and backup codes
- append-only Audit history and immediate/queued Mail/SMS
Parity checklist
The maintained test app and this guide share these visible destinations:
/categoriesand/productsfor the core relationship/attachment example/users,/tags,/posts, and/commentsfor the publishing model/blogfor the composed publishing read experience/realtimefor the explicitly composed Job/SSE/WebSocket demo/login,/register,/account, and/account/securityfor Auth/examples/auditand/examples/messagingfor the operational demos
Each non-Auth destination has an explicit entry in web/src/lib/resources.ts. Auth is contextual in
the sidebar footer. Jobs have no standalone link: progress appears at /realtime, while queued message
delivery appears at /examples/messaging.
Messaging may use Jobs and Audit; it does not require Realtime. Audit does not enqueue or publish. Realtime is transient. Jobs are durable execution.
Repeat migrations and this smoke path against a fresh database. The tutorial contains no unexplained extra resource model, hidden route discovery, pre-existing showcase file, or hidden seed requirement.