Claude Code for Elysia.js: Fast Bun-Native HTTP Framework — Claude Skills 360 Blog
Blog / Backend / Claude Code for Elysia.js: Fast Bun-Native HTTP Framework
Backend

Claude Code for Elysia.js: Fast Bun-Native HTTP Framework

Published: June 2, 2027
Read time: 6 min read
By: Claude Skills 360

Elysia.js is a Bun-native HTTP framework with end-to-end type safety — new Elysia() creates the app. .get("/path", handler, { beforeHandle, schema: { query, params, response } }) registers a typed route. Schema validation uses Elysia’s t type builder (TypeBox-based): t.Object({ id: t.String(), page: t.Optional(t.Numeric()) }). .derive(({ request }) => ({ userId: getUserId(request) })) adds request-scoped context. .guard({ beforeHandle: [isAuthenticated] }) creates a protected scope. .use(swagger()) adds OpenAPI docs at /swagger. .use(bearer()) extracts Bearer tokens. Eden Treaty: const api = treaty<typeof app>("http://localhost:3000") creates a fully typed client — api.posts.get({ query: { page: 1 } }) returns typed data. Plugins: .use(cors()), .use(staticPlugin()), .use(jwt({ name: "jwt", secret })). .listen(3000) starts the server. Claude Code generates Elysia.js APIs, Eden Treaty clients, JWT auth plugins, and OpenAPI-documented route groups.

CLAUDE.md for Elysia.js

## Elysia.js Stack
- Version: elysia >= 1.1, @elysiajs/eden >= 1.1, @elysiajs/swagger >= 1.1
- Init: const app = new Elysia().use(swagger()).use(cors()).listen(3000)
- Route: app.get("/users/:id", ({ params: { id } }) => getUser(id), { params: t.Object({ id: t.String() }) })
- Body: app.post("/users", ({ body }) => createUser(body), { body: t.Object({ name: t.String(), email: t.String() }) })
- Group: app.group("/api", app => app.get("/health", () => ({ ok: true })))
- Guard: app.guard({ beforeHandle: [isAuthenticated] }, app => app.get("/me", ({ user }) => user))
- Derive: app.derive(({ request }) => ({ token: request.headers.get("authorization") }))
- Eden client: const api = treaty<typeof app>("http://localhost:3000")

Elysia App Setup

// src/index.ts — Elysia app with plugins and routes
import { Elysia, t } from "elysia"
import { swagger } from "@elysiajs/swagger"
import { cors } from "@elysiajs/cors"
import { bearer } from "@elysiajs/bearer"
import { jwt } from "@elysiajs/jwt"
import { db } from "./db"
import { users, posts } from "./db/schema"
import { eq, desc, ilike, count } from "drizzle-orm"
import { hash, verify } from "@node-rs/argon2"

// ── JWT plugin ─────────────────────────────────────────────────────────────

const jwtPlugin = jwt({
  name: "jwt",
  secret: process.env.JWT_SECRET!,
  exp: "7d",
})

// ── Auth middleware ────────────────────────────────────────────────────────

const authMiddleware = new Elysia({ name: "auth-middleware" })
  .use(bearer())
  .use(jwtPlugin)
  .derive({ as: "scoped" }, async ({ bearer, jwt, set }) => {
    if (!bearer) {
      set.status = 401
      throw new Error("Unauthorized")
    }

    const payload = await jwt.verify(bearer)
    if (!payload || typeof payload.sub !== "string") {
      set.status = 401
      throw new Error("Invalid token")
    }

    const user = await db.query.users.findFirst({
      where: eq(users.id, payload.sub),
    })

    if (!user) {
      set.status = 401
      throw new Error("User not found")
    }

    return { currentUser: user }
  })

// ── Posts routes ───────────────────────────────────────────────────────────

const postsRoutes = new Elysia({ prefix: "/posts" })
  .get(
    "/",
    async ({ query }) => {
      const { page = 1, pageSize = 10, search } = query
      const offset = (page - 1) * pageSize

      const where = search ? ilike(posts.title, `%${search}%`) : undefined

      const [result, totalResult] = await Promise.all([
        db.query.posts.findMany({
          where,
          limit: pageSize,
          offset,
          orderBy: desc(posts.createdAt),
        }),
        db.select({ count: count() }).from(posts),
      ])

      return {
        posts: result,
        total: totalResult[0]?.count ?? 0,
        page,
        pageSize,
        totalPages: Math.ceil((totalResult[0]?.count ?? 0) / pageSize),
      }
    },
    {
      query: t.Object({
        page: t.Optional(t.Numeric()),
        pageSize: t.Optional(t.Numeric()),
        search: t.Optional(t.String()),
      }),
    },
  )

  .get("/:id", async ({ params: { id }, set }) => {
    const post = await db.query.posts.findFirst({ where: eq(posts.id, id) })
    if (!post) {
      set.status = 404
      return { message: "Post not found" }
    }
    return post
  }, {
    params: t.Object({ id: t.String() }),
  })

  .use(authMiddleware)

  .post(
    "/",
    async ({ body, currentUser, set }) => {
      const slug = body.title
        .toLowerCase()
        .replace(/[^a-z0-9]+/g, "-")
        .replace(/^-|-$/g, "")

      const [post] = await db.insert(posts).values({
        ...body,
        slug,
        authorId: currentUser.id,
      }).returning()

      set.status = 201
      return post
    },
    {
      body: t.Object({
        title: t.String({ minLength: 5, maxLength: 200 }),
        content: t.String({ minLength: 20 }),
        excerpt: t.Optional(t.String({ maxLength: 500 })),
        published: t.Optional(t.Boolean()),
      }),
    },
  )

  .patch(
    "/:id",
    async ({ params: { id }, body, currentUser, set }) => {
      const existing = await db.query.posts.findFirst({ where: eq(posts.id, id) })
      if (!existing) return (set.status = 404, { message: "Not found" })
      if (existing.authorId !== currentUser.id) return (set.status = 403, { message: "Forbidden" })

      const [updated] = await db.update(posts).set(body).where(eq(posts.id, id)).returning()
      return updated
    },
    {
      params: t.Object({ id: t.String() }),
      body: t.Partial(t.Object({
        title: t.String({ minLength: 5 }),
        content: t.String({ minLength: 20 }),
        excerpt: t.String({ maxLength: 500 }),
        published: t.Boolean(),
      })),
    },
  )

  .delete(
    "/:id",
    async ({ params: { id }, currentUser, set }) => {
      const existing = await db.query.posts.findFirst({ where: eq(posts.id, id) })
      if (!existing) return (set.status = 404, { message: "Not found" })
      if (existing.authorId !== currentUser.id) return (set.status = 403, { message: "Forbidden" })

      await db.delete(posts).where(eq(posts.id, id))
      set.status = 204
      return null
    },
    { params: t.Object({ id: t.String() }) },
  )

// ── Auth routes ────────────────────────────────────────────────────────────

const authRoutes = new Elysia({ prefix: "/auth" })
  .use(jwtPlugin)
  .post(
    "/register",
    async ({ body, set }) => {
      const existing = await db.query.users.findFirst({ where: eq(users.email, body.email) })
      if (existing) {
        set.status = 409
        return { message: "Email already in use" }
      }

      const hashedPassword = await hash(body.password)
      const [user] = await db.insert(users).values({
        email: body.email,
        name: body.name,
        hashedPassword,
      }).returning({
        id: users.id,
        email: users.email,
        name: users.name,
        role: users.role,
        createdAt: users.createdAt,
      })

      const token = await jwt.sign({ sub: user.id, email: user.email })
      set.status = 201
      return { user, token }
    },
    {
      body: t.Object({
        email: t.String({ format: "email" }),
        name: t.String({ minLength: 2, maxLength: 100 }),
        password: t.String({ minLength: 8 }),
      }),
    },
  )

  .post(
    "/login",
    async ({ body, set }) => {
      const user = await db.query.users.findFirst({ where: eq(users.email, body.email) })
      if (!user) {
        set.status = 401
        return { message: "Invalid credentials" }
      }

      const valid = await verify(user.hashedPassword ?? "", body.password)
      if (!valid) {
        set.status = 401
        return { message: "Invalid credentials" }
      }

      const token = await jwt.sign({ sub: user.id, email: user.email })
      return {
        user: { id: user.id, email: user.email, name: user.name, role: user.role },
        token,
      }
    },
    {
      body: t.Object({
        email: t.String(),
        password: t.String(),
      }),
    },
  )

// ── Main app ───────────────────────────────────────────────────────────────

export const app = new Elysia()
  .use(swagger({
    documentation: {
      info: { title: "My API", version: "1.0.0" },
      tags: [
        { name: "posts", description: "Post management" },
        { name: "auth", description: "Authentication" },
      ],
    },
  }))
  .use(cors({
    origin: process.env.APP_URL ?? "http://localhost:3000",
    credentials: true,
  }))
  .onError(({ code, error, set }) => {
    if (code === "VALIDATION") {
      set.status = 400
      return { message: "Validation error", issues: (error as any).all }
    }
    if (code === "NOT_FOUND") {
      set.status = 404
      return { message: "Route not found" }
    }
    console.error("[Elysia error]", error)
    set.status = 500
    return { message: "Internal server error" }
  })
  .get("/health", () => ({
    status: "ok",
    timestamp: new Date().toISOString(),
    uptime: process.uptime(),
  }))
  .use(postsRoutes)
  .use(authRoutes)
  .listen(process.env.PORT ?? 3000)

export type App = typeof app

Eden Treaty Client

// lib/api/client.ts — Eden Treaty typed client
import { treaty } from "@elysiajs/eden"
import type { App } from "../../server/src/index"

// Fully type-safe — inferred from server App type
export const api = treaty<App>(
  process.env.NEXT_PUBLIC_API_URL ?? "http://localhost:3000",
  {
    headers() {
      const token = typeof window !== "undefined"
        ? localStorage.getItem("access_token")
        : null
      return token ? { Authorization: `Bearer ${token}` } : {}
    },
  },
)

// Example usage (fully typed):
// const { data, error } = await api.posts.get({ query: { page: 1 } })
// data?.posts  ← Post[]
//
// const { data, error } = await api.auth.login.post({ email, password })
// data?.token  ← string
//
// const { data } = await api.posts({ id }).delete()  // DELETE /posts/:id

For the Hono alternative when deploying to Cloudflare Workers, Deno Deploy, or other edge runtimes beyond Bun is needed — Hono runs everywhere (Node.js, Bun, Cloudflare Workers, Deno) while Elysia is specifically optimized for Bun’s runtime and achieves higher throughput on Bun, see the Hono guide. For the Fastify alternative when a mature Node.js ecosystem with a large plugin registry, JSON Schema validation, and proven production use at scale (millions of requests per day) is preferred over bleeding-edge Bun performance — Fastify has a much larger community and plugin ecosystem while Elysia has a more ergonomic TypeScript API, see the Fastify guide. The Claude Skills 360 bundle includes Elysia.js skill sets covering APIs, Eden Treaty, and JWT auth. Start with the free tier to try Bun-native API generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free