A modern TypeScript server-side rendering starter template with Elysia, Eta templates, JWT authentication, and Prisma ORM.
- π Fast & Type-Safe - Built with Bun and Elysia for blazing fast performance
- π Secure Authentication - JWT-based auth with HTTP-only cookies
- π Server-Side Rendering - Traditional SSR with Eta templates
- π¨ Tailwind CSS - Utility-first CSS framework
- ποΈ Prisma ORM - Type-safe database access
- π Form-Based - Progressive enhancement friendly, no client-side JS required
- π‘οΈ Security Headers - Built-in security best practices
- π Logging - Structured logging with Pino
# Create a new project
bun create github.com/jakswa/elysia-ssr test-app
cd test-app
# load the DB schema (edit DATABASE_URL in .env if needed)
bun run db:push
# Start the development server + tailwindcss watcher
bun run dev
# Or with a process manager (optional, pretty colors)
# Install: gem install foreman
# foreman start -f Procfile.dev
# Or: brew install overmind && overmind start -f Procfile.dev
# Or: cargo install ultraman && ultraman start -f Procfile.devsrc/
βββ controllers/ # Route handlers
βββ middleware/ # Express-style middleware
βββ utils/ # Utility functions
βββ views/ # Eta templates
β βββ layouts/ # Layout templates
β βββ partials/ # Reusable components
βββ styles/ # CSS files
βββ app.ts # App configuration
βββ index.ts # Entry point
Create a .env file based on .env.example:
# Database
DATABASE_URL="postgresql://user:password@localhost:5432/myapp"
# Authentication
JWT_SECRET="your-secret-key-here"
# Server
PORT=3000
NODE_ENV=development# Development
bun run dev # Start dev server + tailwindcss watcher
bun run css:build # Build Tailwind CSS
# Database
bun run db:migrate # Run database migrations
bun run db:reset # Reset database
bun run db:generate # Generate Prisma client
# Production
bun run build # Build for production
bun run start # Start production server
# Code Quality
bun run typecheck # Type check with TypeScript
bun run lint # Lint with Biome
bun run format # Format with Biome- User registers with name, email, and password
- Password is hashed with bcrypt
- JWT token is created and stored in HTTP-only cookie
- Protected routes check for valid JWT token
- User context is available in all routes via middleware
- Create a new controller in
src/controllers/:
import { Elysia } from 'elysia';
import { guard } from '../middleware/auth';
export const myController = new Elysia()
.use(guard) // Add auth guard if needed
.get('/my-route', ({ view, user }) => {
return view('layouts/main', {
title: 'My Page',
user,
body: view('my-template', { user }),
});
});- Add the controller to
app.ts:
import { myController } from './controllers/my-controller';
export const app = new Elysia()
// ... other middleware
.use(myController);- Create your template in
src/views/my-template.eta
Extend the Prisma schema in prisma/schema.prisma:
model Post {
id String @id @default(uuid())
title String
content String
authorId String
author User @relation(fields: [authorId], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}Then run:
bun run db:migrateThe template includes automatic cache busting for CSS:
- Development: Uses timestamps to ensure fresh CSS on every page load
- Production: Can use BUILD_VERSION env var (e.g., git hash) for versioned deployments
# Production build with version
docker build --build-arg BUILD_VERSION=$(git rev-parse --short HEAD) -t my-app .Build and run with Docker:
# Build the image
docker build -t my-app .
# Run the container
docker run -p 3000:3000 --env-file .env my-appDeploy to Fly.io:
# Install flyctl
curl -L https://fly.io/install.sh | sh
# Launch app (first time)
fly launch
# Deploy updates
fly deploy- Build the application:
bun run build-
Set production environment variables
-
Run the production server:
bun run start- Always use HTTPS in production
- Keep dependencies updated
- Use strong JWT secrets
- Enable CORS if needed
- Configure rate limiting for production
- Never commit
.envfiles
MIT