How we ship full-stack apps with Bun, Next.js, and Elysia cover image
Back to Blog
TutorialPublished 15 June 2026· Updated 14 August 2026· 3 min read

How we ship full-stack apps with Bun, Next.js, and Elysia

The stack we use at Agentic Academy Labs: Bun workspaces, Next.js App Router, Elysia APIs, PostgreSQL, and a Docker deploy. What we keep shared and what we refuse to share.

How we ship full-stack apps with Bun, Next.js, and Elysia

This is the layout we use for client MVPs and for internship projects at Agentic Academy Labs. It is not a framework dump. It is the split that has survived more than one handoff.

What we actually share

A Bun workspace with three packages is enough:

  1. apps/web: Next.js App Router. Server components for pages that can be static or SSR. Client components only for forms, chat, and anything that streams.
  2. apps/api: Elysia on Bun. Auth, validation, and database access live here. The website talks to this API over HTTP, including when both run on the same Vercel-adjacent host in development.
  3. packages/types: request and response types both apps import. We do not generate a giant SDK on day one.

PostgreSQL is the default store. We add Redis only when we have a cache or queue that has already hurt us.

What we do in week one

  • Create the workspace and a single users table with a migration.
  • JWT login on the API. The website stores the token the way the product will in production, not a fake cookie that never ships.
  • One authenticated GET and one POST that round-trip through Elysia's validator.
  • A Dockerfile for the API and a standard Next.js build for the web app.

If that slice is not running on a staging URL by the end of week one, the rest of the architecture is theater.

Type sharing without a ceremony

Share the shapes that cross the network: CreateInvoiceInput, InvoiceDto. Do not share React components into the API package. Do not share Elysia plugins into the website.

When a field name changes, we change the type, the validator, and the form in the same pull request. That is the whole point of one repo.

Auth and secrets

API keys for OpenAI, Razorpay, or SMTP never go in NEXT_PUBLIC_*. Route handlers or the Elysia app hold them. The browser gets a session, not a provider key.

We use JWT with a long-lived access token for this product's student and client apps because the UX is a logged-in dashboard, not a bank. If you are building something higher risk, shorten expiry and add refresh. Do not copy our default blindly.

Deploy

The API goes out as a container or a Bun process behind HTTPS. The website is a Next.js build. Environment variables are listed in .env.example without pretending PORT is a secret.

We run migrations as a separate step before traffic hits a new API version. A failed migration is a stop, not a "retry from the UI".

Where people copy us and regret it

  • Putting all business logic in Next.js route handlers, then needing a Flutter app that cannot call those handlers cleanly. That is why we keep Elysia as the API.
  • Sharing a Drizzle schema into the frontend. The frontend should not know column names.
  • Adding LangChain on day one. Add RAG after the CRUD path works. See Next.js AI app architecture.

Internship note

Students in the program build this layout for their capstone. Mentors reject PRs that hide SQL in React components or commit .env. The same bar applies to client work.

If you want this built for a product, schedule a discovery call or read web and mobile development.

Enjoyed this article?

Back to Blog