Skip to content

VormicLab/next-starter-fullstack

Repository files navigation

Next.js Fullstack Starter

Single Next.js 16 process for both frontend and API. Auth (register / login / verify email / forgot+reset password / profile) wired end-to-end with HttpOnly cookie sessions, Prisma + Postgres, and Inngest for the email queue.

Stack

  • Web + API: Next.js 16 (App Router) · React 19 · TypeScript
  • UI: Tailwind v4 · shadcn/ui (new-york, gray) · lucide-react · react-hook-form + zod
  • HTTP client: axios with relative /api base, HttpOnly cookie credentials
  • Database: Prisma + PostgreSQL
  • Auth: HttpOnly cookie · JWT (HS256, jose) · bcryptjs (12 rounds)
  • Background jobs: Inngest (email queue)
  • Email: nodemailer + handlebars

Local Development

Prerequisites

  • Node.js ≥ 20
  • PostgreSQL running locally (or in Docker)
  • Two terminals (one for Next, one for Inngest)

First-time setup

# 1. Install
npm install

# 2. Configure env
cp .env.example .env
# edit .env:
#   - DATABASE_URL → your local Postgres
#   - JWT_SECRET   → any random string ≥ 32 characters
#   - APP_URL      → http://localhost:3000

# 3. Apply database schema
npx prisma migrate dev --name init

Run it

Two processes, two terminals:

# Terminal 1 — web + API
npm run dev
# Terminal 2 — Inngest dev server (runs the email worker)
npx inngest-cli@latest dev

Open:

Email in dev

SMTP_URL is empty by default. The transport detects this and logs the rendered email body to the console instead of sending. You'll see the verify-email / reset-password / welcome-email content directly in your terminal whenever a flow triggers an email — no Mailtrap or SMTP setup needed.

To test against a real inbox, set SMTP_URL=smtp://user:pass@host:port (e.g. a Mailtrap sandbox URL) in .env.

Common dev tasks

Task Command
Update schema → migration npx prisma migrate dev --name <name>
Inspect DB npm run prisma:studio
Regenerate Prisma client npm run prisma:generate
Add shadcn primitive npx shadcn@latest add <name>
Typecheck npx tsc --noEmit
Lint npx eslint

Production

Environment variables

Set these on your hosting platform — never commit production secrets to git.

Var Required Notes
NODE_ENV yes production
APP_URL yes Public origin, e.g. https://app.example.com
DATABASE_URL yes Postgres connection string. Use a connection pooler (PgBouncer / Neon / Supabase) for serverless.
JWT_SECRET yes Long random string (≥32 chars). Rotating this invalidates every active session.
AUTH_COOKIE_NAME no Default auth_token
AUTH_COOKIE_MAX_AGE_SECONDS no Default 604800 (7 days)
SMTP_URL yes (prod) Real SMTP for outbound mail
MAIL_FROM_NAME / MAIL_FROM_EMAIL yes (prod) Sender identity
INNGEST_EVENT_KEY yes (prod, if using Inngest Cloud) From Inngest dashboard
INNGEST_SIGNING_KEY yes (prod, if using Inngest Cloud) From Inngest dashboard
VERIFY_EMAIL_EXPIRY_MINUTES no Default 15
RESET_PASSWORD_EXPIRY_MINUTES no Default 15
MAX_VERIFICATION_SEND_COUNT no Default 5

The cookie is automatically set with Secure: true when NODE_ENV=production — make sure your prod URL is HTTPS.

Build and run

npm install --omit=dev
npm run prisma:generate
npm run build
npm run start                # serves on $PORT (default 3000)

Run database migrations as a separate step before rolling out new code:

npx prisma migrate deploy

Inngest in production

Two options. Pick one:

Option A — Inngest Cloud (managed, easiest)

  1. Create an app at https://app.inngest.com.

  2. Copy the Event Key and Signing Key into your env (INNGEST_EVENT_KEY, INNGEST_SIGNING_KEY).

  3. Register your function endpoint with Inngest:

    curl -X PUT https://<your-domain>/api/inngest

    (or use the "Sync" button in the Inngest dashboard.)

That's it — the SDK posts events to Inngest Cloud, and Inngest invokes your /api/inngest endpoint to run functions. Free tier: 50K runs/month.

Option B — Self-host Inngest

Run the open-source Inngest binary on your own infra (Docker, Kubernetes, bare metal — see https://github.com/inngest/inngest). Then point your app at it:

INNGEST_BASE_URL=https://inngest.your-infra.example.com
INNGEST_EVENT_KEY=<key you set on the self-hosted instance>
INNGEST_SIGNING_KEY=<key you set on the self-hosted instance>

Caveat: the OSS binary is newer than the cloud version. Fine for low/medium traffic; load-test before betting heavily on it.

Deployment targets

Target Notes
Vercel Works out of the box. Use Inngest Cloud (Option A). Use a connection pooler for DATABASE_URL since each serverless invocation can open a new connection.
Docker Standard next start container. npm run build then ship the whole project (or use Next's standalone output). Run prisma migrate deploy from a separate one-shot job before rolling out.
Node.js server (Render / Fly / Railway / your own VM) Same as Docker. Add a health check on /.
Static export Not supported — this app needs route handlers, cookies, and Prisma.

Health checklist before going live

  • JWT_SECRET is a fresh, long random value (not the dev one)
  • APP_URL is HTTPS
  • DATABASE_URL points to your prod Postgres, with pooling if serverless
  • SMTP_URL set to real outbound SMTP (otherwise emails are silently logged, never sent)
  • INNGEST_* keys set (Cloud) or INNGEST_BASE_URL set (self-host)
  • Inngest endpoint synced (Cloud only — curl -X PUT https://<domain>/api/inngest)
  • prisma migrate deploy run against prod DB
  • HTTPS works end-to-end so the Secure cookie attribute doesn't break login

Project structure

See AGENTS.md for the full folder-by-folder breakdown, API contract, and conventions.

License

MIT.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors