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 🙏

© 2026 – Pkg Stats / Ryan Hefner

vike-api-router

v0.1.11

Published

File-based API routing for Vike — supports Hono, h3, and more

Readme

vike-api-router

[!WARNING] Proof of concept — partially works, not well tested. Core features (file-based routes, middleware, proxy) are functional and used in real projects. Advanced features (defineRoute, defineProxyRoute, SSR/client handler unification) are experimental and may have edge cases. API may change. Validate behavior against your own setup before relying on it.

File-based API routing for Vike. Define your server endpoints by creating files — no manual route registration needed.

Works with any server framework supported by universal-middleware: Hono, h3, Express, and more.


File Structure

server/
  api/          → HTTP endpoints with /api/ prefix
  routes/       → HTTP endpoints without prefix (robots.txt, sitemaps, etc.)
  handlers.ts   → RPC handlers, callable from client code directly

File naming

| File | Route | |------|-------| | server/api/get.ts | GET /api | | server/api/users/get.ts | GET /api/users | | server/api/users/post.ts | POST /api/users | | server/api/users/all.ts | any method → /api/users | | server/api/users/@id/get.ts | GET /api/users/:id | | server/api/users/@id/delete.ts | DELETE /api/users/:id | | server/api/files/@...rest/get.ts | GET /api/files/* | | server/api/(auth)/sign-in/post.ts | POST /api/sign-in | | server/routes/robots.txt/get.ts | GET /robots.txt |

Segment conventions:

  • @id:id (dynamic param)
  • @...rest* (wildcard)
  • (group) → ignored in the URL path, useful for organization
  • Regular folder names → literal path segments

No + prefix needed — files are named directly (get.ts, post.ts, middleware.ts). This avoids conflicts with Vike's own + file conventions.


Route Handlers

Each get.ts, post.ts, etc. exports a default function:

// server/api/users/@id/get.ts
import type { ApiContext } from 'vike-api-router'

export default async function({ params, req }: ApiContext<{ id: string }>) {
  return { id: params.id, name: 'Alice' }
}

Return values:

  • Response → passed through as-is
  • plain object → serialized as JSON with status 200
  • null / undefined204 No Content

Middleware

Create a middleware.ts file in any directory. It applies cumulatively to all routes in that directory and all subdirectories.

// server/api/middleware.ts — runs for ALL /api/* routes
import type { MiddlewareFn } from 'vike-api-router'

const middleware: MiddlewareFn = async (req, next) => {
  console.log(`${req.method} ${new URL(req.url).pathname}`)
  return next()
}

export default middleware
// server/api/users/middleware.ts — runs only for /api/users/* (stacks on top)
import type { MiddlewareFn } from 'vike-api-router'

const middleware: MiddlewareFn = async (req, next) => {
  if (!req.headers.get('x-api-key')) {
    return new Response(JSON.stringify({ error: 'Unauthorized' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    })
  }
  return next()
}

export default middleware

Middleware chain for GET /api/users/1:

  1. server/api/middleware.ts
  2. server/api/users/middleware.ts
  3. server/api/users/@id/get.ts (route handler)

RPC Handlers

Create server/handlers.ts and export a default object mapping handler names to objects with async methods.

// server/handlers.ts
export default {
  userHandler: {
    async getUser(id: string) {
      return db.users.find(id)
    },
    async createUser(data: { name: string }) {
      return db.users.create(data)
    },
  },
}

Import from vike-api-router/handlers in any page, component, or data hook — the same import works in both SSR and the browser:

import { userHandler } from 'vike-api-router/handlers'

const user = await userHandler.getUser('123')
  • SSR — calls the handler function directly, no HTTP round-trip
  • Browser — makes a POST /_rpc/userHandler/getUser request under the hood

TypeScript types are automatically generated into handlers.d.ts at your project root when the Vite dev server starts. No manual declaration file needed.


Route as Handler — defineRoute

Use defineRoute to declare a route that is also callable as a handler from client code.

// server/api/users/@id/put.ts
import { defineRoute } from 'vike-api-router'
import type { ApiContext } from 'vike-api-router'

export const updateUser = defineRoute({
  async handler({ params, req }: ApiContext<{ id: string }>) {
    const body = await req.json()
    return db.users.update(params.id, body)
  },
})
// Client or +data.ts — same import, works in SSR and browser
import { updateUser } from 'vike-api-router/handlers'

await updateUser({ params: { id: '42' }, body: { name: 'Alice' } })
  • SSR — calls the handler function directly, no HTTP round-trip
  • Browser — makes a real PUT /api/users/42 request

The named export (updateUser) is the handler name. Only one named defineRoute export is allowed per route file — a second one throws an error at scan time.

TypeScript types for updateUser are auto-generated into handlers.d.ts alongside regular handler types.


Proxy

Import from vike-api-router/proxy.

proxyRoute — HTTP proxy endpoint

Returns a RouteHandler that forwards all requests to an upstream target. Use as the default export of a +all.ts file.

// server/api/github/@...path/all.ts
import { proxyRoute } from 'vike-api-router/proxy'

export default proxyRoute({
  target: 'https://api.github.com',

  headers: {
    Authorization: `Bearer ${process.env.GITHUB_TOKEN}`,
  },
})

GET /api/github/repos/foo/barGET https://api.github.com/repos/foo/bar

Method, query string, and body are forwarded automatically. The upstream Response is returned as-is.

Options:

type ProxyRouteOptions = {
  target: string | URL

  // Extra headers to add (static or dynamic)
  headers?: HeadersInit | ((ctx: ApiContext) => HeadersInit | Promise<HeadersInit>)

  // Which incoming headers to forward. Default: ['accept', 'accept-language', 'content-type', 'user-agent']
  forwardHeaders?: boolean | string[]

  // Headers to always strip. Default: ['host', 'connection', 'content-length']
  stripHeaders?: string[]

  // Override the forwarded path
  rewritePath?: (ctx: ApiContext) => string

  // Override the full upstream URL
  rewriteUrl?: (url: URL, ctx: ApiContext) => URL | Promise<URL>

  // Intercept/modify the outgoing request (return Response to short-circuit)
  onRequest?: (request: Request, ctx: ApiContext) => Request | Response | Promise<Request | Response>

  // Intercept/modify the upstream response
  onResponse?: (response: Response, ctx: ApiContext) => Response | Promise<Response>
}

proxyHandler — server-side HTTP client

Returns a typed HTTP client for use inside server handler files. Handles JSON serialization, query params, and throws ProxyError on non-2xx responses.

// server/handlers.ts
import { proxyHandler } from 'vike-api-router/proxy'

const github = proxyHandler({
  target: 'https://api.github.com',
  headers: { Authorization: `Bearer ${process.env.GITHUB_TOKEN}` },
})

export async function getRepo(owner: string, repo: string) {
  return github.get(`/repos/${owner}/${repo}`)
}

Methods: get, post, put, patch, delete, request(method, path, options?)

Request options:

type RequestOptions = {
  query?: Record<string, unknown>   // serialized to URLSearchParams
  body?: unknown                    // JSON.stringify'd, sets content-type automatically
  headers?: HeadersInit
  signal?: AbortSignal
}

Options:

type ProxyHandlerOptions = {
  target: string | URL
  headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>)
  fetch?: typeof fetch
  onRequest?: (request: Request) => Request | Promise<Request>
  onResponse?: (response: Response) => Response | Promise<Response>
}

Error handling:

import { ProxyError } from 'vike-api-router/proxy'

try {
  await github.get('/repos/missing')
} catch (error) {
  if (error instanceof ProxyError) {
    console.log(error.status)   // HTTP status code
    console.log(error.data)     // parsed response body
  }
}
  • 204 → returns undefined
  • non-2xx → throws ProxyError

Setup

1. Vite plugin

// vite.config.ts
import { defineConfig } from 'vite'
import vike from 'vike/plugin'
import { vikeApiRouter } from 'vike-api-router/plugin'

export default defineConfig({
  plugins: [
    vikeApiRouter(), // must come before vike()
    vike(),
  ],
})

Plugin options:

vikeApiRouter({
  serverDir: 'server',  // default
  apiPrefix: '/api',    // default
  rpcPrefix: '/_rpc',   // default
})

2. Server entry

Hono

// +server.ts
import { Hono } from 'hono'
import vike from '@vikejs/hono'
import { vikeApiRouterMiddleware } from 'vike-api-router'

const app = new Hono()

vike(app, [vikeApiRouterMiddleware])

export default app

h3

// +server.ts
import { createApp, toWebHandler } from 'h3'
import vike from '@vikejs/h3'
import { vikeApiRouterMiddleware } from 'vike-api-router'

const app = createApp()

vike(app, [vikeApiRouterMiddleware])

export default { fetch: toWebHandler(app) }

How It Works

  1. Vite plugin scans server/ at startup and on file changes, generates virtual modules (virtual:vike-api-router/routes, virtual:vike-api-router/handlers).
  2. vikeApiRouterMiddleware is a universal-middleware compatible function — it works with any framework adapter (@vikejs/hono, @vikejs/h3, etc.).
  3. On each request, the middleware checks RPC paths first (/_rpc/*), then matches against the route manifest.
  4. Middleware chains are built statically per route (root → leaf) with no runtime overhead per request.

Status

| Feature | Status | |---------|--------| | get/post/put/patch/delete/head/options.ts | ✅ | | all.ts (any method) | ✅ | | middleware.ts (cumulative) | ✅ | | Dynamic segments @id | ✅ | | Wildcard segments @...rest | ✅ | | server/routes/ (no prefix) | ✅ | | RPC handlers (server/handlers.ts) | ✅ | | Client import (vike-api-router/handlers) | ✅ | | HMR (hot reload on file add/remove) | ✅ | | TypeScript | ✅ | | Hono | ✅ | | h3 | 🚧 untested | | Express | 🚧 untested |