Plugins

Install only the plugins you need to keep your server lightweight. All are published on JSR under the @goddo scope.

deno add jsr:@goddo/core jsr:@goddo/html jsr:@goddo/cors

HTML SSR

@goddo/html

Zero-build Server-Side Rendering by compiling TSX natively. Acts as a custom JSX runtime (equivalent to @elysiajs/html), converting JSX elements and Async Components directly to HTML strings, with built-in XSS protection.

deno add jsr:@goddo/html

Ensure Deno uses the custom JSX compiler (deno.json):

{
  "compilerOptions": {
    "jsx": "react-jsx",
    "jsxImportSource": "@goddo/html"
  }
}
import { Goddo } from '@goddo/core'
import { html } from '@goddo/html'

const UserProfile = async ({ id }: { id: string }) => {
  const data = await db.getUser(id)
  return <div>{data.name}</div>
}

new Goddo()
  .use(html())
  .get('/page', () => (
    <html lang='en'>
      <body>
        <h1>Hello Goddo TSX! 🦕</h1>
        <p>Safe: {'<script>alert(1)</script>'}</p>
        <UserProfile id='42' />
      </body>
    </html>
  ))
  .listen(3000)

OpenAPI

@goddo/openapi

Equivalent to @elysiajs/swagger: generates OpenAPI 3.0.3 from routes and t schemas, serving the modern Scalar UI (default) or the classic Swagger UI.

deno add jsr:@goddo/openapi
import { Goddo, t } from '@goddo/core'
import { openapi } from '@goddo/openapi'

new Goddo()
  .use(openapi({
    provider: 'swagger-ui', // optional, default is 'scalar'
    documentation: { info: { title: 'My API', version: '1.0.0' } },
  }))
  .get('/user/:id', ({ params: { id } }) => id, {
    params: t.Object({ id: t.Numeric() }),
    detail: { summary: 'Fetch user', tags: ['user'] },
  })
  .listen(3000)
// UI:   GET /docs
// Spec: GET /docs/json

Options: path (default '/docs'), provider ('scalar' | 'swagger-ui'), documentation (base OpenAPI document), detail per route (summary, description, tags, hide, ...), exclude (paths excluded from spec), bearerAuth (JWT config shortcut), scalarConfig and version (CDN version).

Static Files

@goddo/static

Serves static files from a local directory, with automatic MIME type detection, range request support, cache headers, and path-traversal protection.

deno add jsr:@goddo/static
import { Goddo } from '@goddo/core'
import { staticPlugin } from '@goddo/static'

new Goddo()
  .use(staticPlugin({
    assets: './public', // directory to serve (default: 'public')
    prefix: '/static', // URL prefix (default: '/public')
    maxAge: 86400, // Cache-Control max-age in seconds
    // noCache: true, // send Cache-Control: no-store instead
    // indexHTML: 'index.html', // served at directory roots
    // headers: { 'X-Powered-By': 'Goddo' }, // extra response headers
  }))
  .listen(3000)
// GET /static/logo.png → ./public/logo.png

JWT

@goddo/jwt

Zero-dependency JWT plugin built on the Web Crypto API (HS256/HS384/HS512). Injects a jwt object into the context with sign and verify.

deno add jsr:@goddo/jwt
import { Goddo } from '@goddo/core'
import { jwt } from '@goddo/jwt'

new Goddo()
  .use(jwt({
    secret: Deno.env.get('JWT_SECRET')!,
    alg: 'HS256', // default
    exp: 604800, // optional default expiration: 7 days (seconds)
    // name: 'jwt', // key injected into context (default: 'jwt')
  }))
  .post('/login', async ({ jwt, body }) => ({
    token: await jwt.sign({ sub: body.userId, role: 'user' }),
  }))
  .get('/me', async ({ jwt, headers, error }) => {
    const token = headers.authorization?.replace('Bearer ', '')
    const payload = await jwt.verify(token ?? '')
    if (!payload) throw error(401, 'Unauthorized')
    return payload
  })
  .listen(3000)

jwt.sign(payload) returns a JWT string. jwt.verify(token) returns the payload object or false when the token is invalid or expired.

Bearer

@goddo/bearer

Extracts the token following RFC 6750: first from the Authorization: Bearer <token> header, with fallback to the access_token query param. Injected into the context as bearer.

deno add jsr:@goddo/bearer
import { Goddo } from '@goddo/core'
import { bearer } from '@goddo/bearer'

new Goddo()
  .use(bearer())
  .get('/protected', ({ bearer }) => bearer, {
    beforeHandle({ bearer, set }) {
      if (!bearer) {
        set.status = 401
        set.headers['www-authenticate'] = 'Bearer realm="sign"'
        return 'Unauthorized'
      }
    },
  })
  .listen(3000)

CORS

@goddo/cors

Configurable Cross-Origin Resource Sharing: origin, methods, headers, credentials, and preflight cache.

deno add jsr:@goddo/cors
import { Goddo } from '@goddo/core'
import { cors } from '@goddo/cors'

new Goddo()
  .use(cors({ origin: true, credentials: true }))
  .listen(3000)

Rate Limit

@goddo/rate-limit

Protects endpoints from abuse by limiting requests per IP address. Goddo automatically captures the client IP via Deno.ServeHandlerInfo.

deno add jsr:@goddo/rate-limit
import { rateLimit } from '@goddo/rate-limit'

app.use(rateLimit({ max: 100, windowMs: 60000 })) // 100 req/min

Shield

@goddo/shield

Automatically injects standard HTTP security headers, such as X-Frame-Options, X-Content-Type-Options, and Strict-Transport-Security.

deno add jsr:@goddo/shield
import { shield } from '@goddo/shield'

app.use(shield())

CSRF

@goddo/csrf

Implements the Double Submit Cookie pattern to protect state-mutating endpoints from CSRF.

deno add jsr:@goddo/csrf
import { csrf } from '@goddo/csrf'

app.use(csrf())

Cron

@goddo/cron

Zero-dependency background task scheduling, with its own cron pattern parser (5 or 6 fields, lists, ranges, and steps).

deno add jsr:@goddo/cron
import { Goddo } from '@goddo/core'
import { cron } from '@goddo/cron'

new Goddo()
  .use(
    cron({
      name: 'heartbeat',
      pattern: '0-59/10 * * * * *', // every 10 seconds
      run() {
        console.log('Heartbeat')
      },
    }),
  )
  .get('/stop', ({ store }) => {
    store.cron.heartbeat.stop()
    return 'Stopped'
  })
  .listen(3000)

Server Timing

@goddo/server-timing

Measures the duration of each request lifecycle phase and reports it in the Server-Timing header, visible in the browser DevTools.

deno add jsr:@goddo/server-timing
import { Goddo } from '@goddo/core'
import { serverTiming } from '@goddo/server-timing'

new Goddo()
  .use(serverTiming())
  .get('/', () => 'Hello')
  .listen(3000)

AI Docs (llms.txt)

@goddo/llms-txt

Generates an /llms.txt endpoint reusing the route tree and TypeBox schemas to produce LLM-friendly Markdown documentation for AI agents and LLMs.

deno add jsr:@goddo/llms-txt
import { Goddo, t } from '@goddo/core'
import { llmstxt } from '@goddo/llms-txt'

new Goddo()
  .use(llmstxt({
    title: 'My Custom API',
    description: 'Documentation optimized for LLMs',
    exclude: ['/docs', '/docs/json'],
  }))
  .get('/user/:id', ({ params: { id } }) => id, {
    params: t.Object({ id: t.Numeric({ description: 'User ID' }) }),
    detail: { summary: 'Fetch user' },
  })
  .listen(3000)
// AI Docs: GET /llms.txt

Options: path (default '/llms.txt'), title, description, and exclude (paths excluded from spec).

Treaty (client)

@goddo/treaty

End-to-end type-safe HTTP client generated at compile time from the app's route types — equivalent to Elysia Eden. No code generation: everything is inferred via TypeScript generics and a runtime Proxy.

deno add jsr:@goddo/treaty
import { treaty } from '@goddo/treaty'
import type { App } from './server.ts'

const client = treaty<App>('http://localhost:3000')

// GET /user/1
const { data, error } = await client.user({ id: '1' }).get()

// POST /user (body type enforced by the route schema)
const { data: created } = await client.user.post({
  body: { name: 'Carlos', age: 25 },
})

Path Mapping

RouteTreaty call
GET /client.get()
GET /userclient.user.get()
POST /userclient.user.post({ body: ... })
GET /user/:idclient.user({ id: '1' }).get()
GET /user/:id/postsclient.user({ id: '1' }).posts.get()
GET /api/v1/statusclient.api.v1.status.get()
Coming from Elysia Eden?

Note two small syntax differences for better TypeScript predictability:

  1. The root path (/) is called directly on the client (client.get()), not via client.index.get().
  2. Request payloads must be explicitly wrapped in a body key (e.g., .post({ body: { ... } })), keeping them neatly separated from query and headers.
Reserved Path Segments

Because Goddo Treaty exposes HTTP methods directly at each level (e.g. client.get()), path segments matching HTTP methods ( get, post, put, delete, patch, head, options, subscribe) are reserved. Creating a route like .get('/api/get/users', ...) would silently collide with the .get() proxy method. To prevent this, Goddo enforces a compile-time type error if you attempt to register a route containing a reserved segment.

Response Shape

Every call returns Promise<{ data, error, status, headers, response }>:

const { data, error, status } = await client.user.get()

if (error) {
  console.error(error.message, status) // error is an Error instance
} else {
  console.log(data) // typed as the route's return type
}

Global Options

const client = treaty<App>('http://localhost:3000', {
  headers: { Authorization: 'Bearer token' }, // merged into every request
})

WebSocket

const ws = client.chat.subscribe()
ws.onmessage = (e) => console.log(e.data)