npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@sylphx/gust-app

v0.1.9

Published

Stateless HTTP application framework - portable across Bun, Deno, Edge, Serverless

Readme

@sylphx/gust-app

Stateless HTTP application framework - portable across Bun, Deno, Edge, Serverless

npm License

Features

  • Portable - Works on Bun, Deno, Cloudflare Workers, AWS Lambda, Vercel Edge
  • Stateless - Pure Request -> Response design, no server state
  • Type-safe - Full TypeScript with path parameter inference
  • Zero dependencies - Only depends on @sylphx/gust-core
  • WASM-powered - Radix Trie router and random generation via WebAssembly

Built-in Middleware

| Category | Features | |----------|----------| | Security | CORS, CSRF, Security headers, Rate limiting | | Auth | Basic, Bearer, API Key, HMAC, JWT, Session | | Performance | Compression, Caching, Body limits | | Utilities | Validation, Body parsing, Cookies, Proxy headers | | Observability | Request tracing, Logging |

When to Use

Use @sylphx/gust-app when you want:

  • Serverless - AWS Lambda, Cloudflare Workers, Vercel Edge Functions
  • Portable code - Same app code across different runtimes
  • BYO server - Use with Bun.serve(), Deno.serve(), or any fetch-compatible runtime
  • Testing - Easy to test without starting a server

For a full HTTP server with native acceleration, see @sylphx/gust-server or @sylphx/gust.

Installation

bun add @sylphx/gust-app
# or
npm install @sylphx/gust-app

Quick Start

import { createApp, get, post, json, cors } from '@sylphx/gust-app'

const app = createApp({
  routes: [
    get('/', () => json({ message: 'Hello World' })),
    get('/users/:id', ({ ctx }) => json({ id: ctx.params.id })),
    post('/users', async ({ ctx }) => {
      const body = await ctx.json()
      return json({ created: body })
    }),
  ],
  middleware: cors(),
})

// Use with any runtime
Bun.serve({ fetch: app.fetch, port: 3000 })
// or
Deno.serve({ port: 3000 }, app.fetch)
// or
export default { fetch: app.fetch }  // Cloudflare Workers

API

createApp

Creates a stateless application instance.

import { createApp, get, json, cors, rateLimit, compose } from '@sylphx/gust-app'

const app = createApp({
  // Routes (required)
  routes: [
    get('/', () => json({ hello: 'world' })),
  ],

  // Global middleware (optional)
  middleware: compose(
    cors(),
    rateLimit({ max: 100, window: 60000 }),
  ),

  // 404 handler (optional)
  notFound: () => json({ error: 'Not Found' }, { status: 404 }),

  // Error handler (optional)
  onError: (error) => json({ error: error.message }, { status: 500 }),
})

// App instance provides:
app.fetch      // (Request) => Promise<Response>
app.handle     // (Context) => Promise<ServerResponse>
app.manifest   // RouteManifest for native registration

Route Builders

import { get, post, put, patch, del, head, options, all, routes } from '@sylphx/gust-app'

// HTTP methods
get('/path', handler)
post('/path', handler)
put('/path', handler)
patch('/path', handler)
del('/path', handler)    // 'delete' is reserved
head('/path', handler)
options('/path', handler)
all('/path', handler)    // Match any method

// Route grouping with prefix
const apiRoutes = routes('/api', [
  get('/users', listUsers),
  get('/users/:id', getUser),
  post('/users', createUser),
])

// Nested groups
const v1Routes = routes('/v1', [
  ...routes('/users', [
    get('/', list),
    get('/:id', show),
  ]),
])

Handler Signature

import { get, json, type HandlerArgs, type Context } from '@sylphx/gust-app'

// Handler receives { ctx, input }
get('/users/:id', ({ ctx, input }) => {
  // ctx properties
  ctx.method     // 'GET', 'POST', etc.
  ctx.path       // '/users/123'
  ctx.query      // '?foo=bar' query string
  ctx.headers    // Record<string, string>
  ctx.params     // { id: '123' } - typed from path!
  ctx.body       // Raw body buffer
  ctx.json<T>()  // Parse JSON body
  ctx.app        // User-defined app context
  ctx.request    // Original Request (for delegation)

  return json({ id: ctx.params.id })
})

External Handler Integration

Seamlessly integrate fetch-based handlers like GraphQL Yoga, tRPC, Hono:

import { createApp, all } from '@sylphx/gust-app'
import { createYoga, createSchema } from 'graphql-yoga'

const yoga = createYoga({
  schema: createSchema({
    typeDefs: `type Query { hello: String }`,
    resolvers: { Query: { hello: () => 'Hello!' } },
  }),
})

const app = createApp({
  routes: [
    // Direct integration - auto-detects fetch-style handlers!
    all('/graphql', yoga.fetch),
  ],
})

Works with any fetch-compatible handler:

import { Hono } from 'hono'
import { fetchRequestHandler } from '@trpc/server/adapters/fetch'

// Hono
const hono = new Hono()
hono.get('/hello', (c) => c.json({ hello: 'world' }))
all('/api/*', hono.fetch)

// tRPC
all('/trpc/*', (req) => fetchRequestHandler({ req, router, endpoint: '/trpc' }))

Handlers can return either ServerResponse or standard Response - both work automatically.

Response Helpers

import {
  json,
  text,
  html,
  redirect,
  response,
  notFound,
  badRequest,
  unauthorized,
  forbidden,
  serverError,
} from '@sylphx/gust-app'

json({ data: 'value' })              // application/json
json({ error: 'msg' }, { status: 400 })
text('Hello')                        // text/plain
html('<h1>Hello</h1>')               // text/html
redirect('/new-path')                // 302 redirect
redirect('/new-path', 301)           // 301 redirect
response(body, { status, headers })  // Custom response

// Error responses
notFound()         // 404
badRequest()       // 400
unauthorized()     // 401
forbidden()        // 403
serverError()      // 500

Middleware

import {
  // Composition
  compose,
  pipe,

  // Security
  cors,
  simpleCors,
  security,
  strictSecurity,
  apiSecurity,

  // Auth
  basicAuth,
  bearerAuth,
  apiKeyAuth,
  jwtAuth,
  hmacAuth,

  // Session
  session,
  csrf,

  // Performance
  compress,
  cache,
  etag,
  bodyLimit,
  rateLimit,

  // Utilities
  tracing,
  logging,
  proxy,
  validate,
} from '@sylphx/gust-app'

// Compose middleware (right-to-left)
const middleware = compose(
  cors(),
  rateLimit({ max: 100, window: 60000 }),
  jwtAuth({ secret: 'your-secret' }),
)

// Or pipe (left-to-right)
const middleware = pipe(
  handler,
  jwtAuth({ secret: 'your-secret' }),
  cors(),
)

Validation

import {
  validate,
  validateBody,
  validateQuery,
  getValidated,
  getValidatedQuery,
  object,
  string,
  number,
  boolean,
  array,
  email,
  url,
  uuid,
  optional,
  nullable,
} from '@sylphx/gust-app'

const createUser = post('/users', compose(
  validate({
    body: object({
      name: string({ minLength: 1, maxLength: 100 }),
      email: email(),
      age: optional(number({ min: 0 })),
      tags: array(string()),
    }),
  }),
  ({ ctx }) => {
    const data = getValidated(ctx)
    // data is typed: { name: string, email: string, age?: number, tags: string[] }
    return json(data)
  }
))

JWT Authentication

import { jwtAuth, optionalJwt, createJwt, verifyJwt, getJwtPayload } from '@sylphx/gust-app'

// Middleware
const protected = jwtAuth({ secret: 'your-secret' })
const optionalAuth = optionalJwt({ secret: 'your-secret' })

// Manual JWT operations
const token = await createJwt(
  { userId: '123', role: 'admin' },
  'your-secret',
  { expiresIn: '1h' }
)

const payload = await verifyJwt(token, 'your-secret')

// In handler
get('/me', compose(
  jwtAuth({ secret: 'your-secret' }),
  ({ ctx }) => {
    const payload = getJwtPayload(ctx)
    return json(payload)
  }
))

Session & CSRF

import { session, csrf, getSession, getCsrfToken, flash } from '@sylphx/gust-app'

const app = createApp({
  routes: [
    get('/form', ({ ctx }) => html(`
      <form method="POST" action="/submit">
        <input type="hidden" name="_csrf" value="${getCsrfToken(ctx)}">
        <button>Submit</button>
      </form>
    `)),

    post('/submit', ({ ctx }) => {
      const sess = getSession(ctx)
      sess.data.visits = ((sess.data.visits as number) || 0) + 1
      return json({ visits: sess.data.visits })
    }),
  ],
  middleware: compose(
    session({ secret: 'session-secret' }),
    csrf({ secret: 'csrf-secret' }),
  ),
})

Rate Limiting

import { rateLimit, rateLimitWithStore } from '@sylphx/gust-app'

// In-memory (default)
rateLimit({
  max: 100,           // Max requests
  window: 60000,      // Per window (ms)
  keyGenerator: (ctx) => ctx.headers['x-forwarded-for'] || 'anonymous',
})

// With custom store (Redis, etc.)
rateLimitWithStore(redisStore, {
  max: 100,
  window: 60000,
})

Typed App Context

import { createApp, createRouter, type Context } from '@sylphx/gust-app'

// Define your app context
type App = {
  db: Database
  user: User | null
  requestId: string
}

// Create typed route builders
const { get, post, routes } = createRouter<App>()

const app = createApp({
  routes: [
    get('/users', ({ ctx }) => {
      // ctx.app.db is typed!
      return json(ctx.app.db.getUsers())
    }),

    get('/me', ({ ctx }) => {
      // ctx.app.user is typed!
      return ctx.app.user
        ? json(ctx.app.user)
        : unauthorized()
    }),
  ],

  // Provide app context per request
  context: (baseCtx) => ({
    db: getDatabase(),
    user: getUserFromToken(baseCtx.headers.authorization),
    requestId: crypto.randomUUID(),
  }),
})

Use with Different Runtimes

Bun

Bun.serve({
  fetch: app.fetch,
  port: 3000,
})

Deno

Deno.serve({ port: 3000 }, app.fetch)

Cloudflare Workers

export default {
  fetch: app.fetch,
}

AWS Lambda

import { createApp, get, json } from '@sylphx/gust-app'

const app = createApp({
  routes: [get('/', () => json({ hello: 'lambda' }))],
})

export const handler = async (event: APIGatewayProxyEventV2) => {
  const request = new Request(
    `https://lambda.local${event.rawPath}`,
    {
      method: event.requestContext.http.method,
      headers: event.headers as HeadersInit,
      body: event.body,
    }
  )

  const response = await app.fetch(request)

  return {
    statusCode: response.status,
    headers: Object.fromEntries(response.headers),
    body: await response.text(),
  }
}

Node.js HTTP

import { createServer } from 'node:http'
import { createApp, get, json } from '@sylphx/gust-app'

const app = createApp({
  routes: [get('/', () => json({ hello: 'node' }))],
})

createServer(async (req, res) => {
  const url = new URL(req.url!, `http://${req.headers.host}`)
  const request = new Request(url, {
    method: req.method,
    headers: req.headers as HeadersInit,
  })

  const response = await app.fetch(request)

  res.statusCode = response.status
  response.headers.forEach((v, k) => res.setHeader(k, v))
  res.end(await response.text())
}).listen(3000)

Bundle Size

| Component | Size | Gzipped | |-----------|------|---------| | JavaScript | 82 KB | 18 KB | | Type Definitions | 47 KB | 12 KB | | WASM (router) | 27 KB | 12 KB |

Tree-shaking removes unused middleware in production builds.

License

MIT


Built with Sylphx