Skip to content

🧭 A high-performance API router for Astro, powered by a Trie-based matcher. Define clean route handlers with full HTTP method support, dynamic params, and blazing-fast lookup β€” without nested src/pages/api/ clutter.

License

Notifications You must be signed in to change notification settings

oamm/astro-routify

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

20 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

astro-routify

A high-performance API router for Astro built on a Trie matcher.
Define API routes using clean, flat structures β€” no folders or boilerplate logic.

npm license downloads feedback-welcome


Installing

npm install astro-routify

⚑️ Quickstart

// src/pages/api/index.ts
import {
    defineRoute,
    defineRouter,
    defineGroup,
    HttpMethod,
    ok,
} from 'astro-routify';

const userGroup = defineGroup('/users', (group) => {
    group.addGet('/:id', ({params}) => ok({id: params.id}));
});

export const GET = defineRouter([
    defineRoute(HttpMethod.GET, '/ping', () => ok('pong')),
    ...userGroup.getRoutes(),
]);

Or to handle everything in a single place:

import {RouterBuilder, ok} from 'astro-routify';

const builder = new RouterBuilder();

builder
    .addGet('/ping', () => ok('pong'))
    .addPost('/submit', async ({request}) => {
        const body = await request.json();
        return ok({received: body});
    });

export const ALL = builder.build(); // catch-all

πŸ’‘ Full Example

You can find an implementation example in the astro-routify-example repository. It showcases a minimal Astro app with API endpoints configured under:

/src/pages/api/[...path].ts

This setup demonstrates how to route requests dynamically using astro-routify, while still leveraging Astro's native endpoint system.


πŸš€ Features

  • ⚑ Fully compatible with Astro’s native APIContext β€” no extra setup needed.
  • 🧩 Use middleware, access cookies, headers, and request bodies exactly as you would in a normal Astro endpoint.
  • βœ… Flat-file, code-based routing (no folders required)
  • βœ… Dynamic segments (:id)
  • βœ… ALL-mode for monolithic routing (RouterBuilder)
  • βœ… Built-in response helpers (ok, created, etc.)
  • βœ… Trie-based matcher for fast route lookup
  • βœ… Fully typed β€” no magic strings
  • πŸ” Streaming support
    • stream() β€” raw streaming with backpressure support (e.g. SSE, logs, custom protocols)
    • streamJsonND() β€” newline-delimited JSON streaming (NDJSON)
    • streamJsonArray() β€” server-side streamed JSON arrays

πŸ”„ See CHANGELOG.md for recent updates and improvements.


🧠 Core Concepts

defineRoute()

Declare a single route:

defineRoute(HttpMethod.GET, "/users/:id", ({params}) => {
    return ok({userId: params.id});
});

defineRouter()

Group multiple routes under one HTTP method handler:

export const GET = defineRouter([
    defineRoute(HttpMethod.GET, "/health", () => ok("ok"))
]);

🧠 defineRouter() supports all HTTP methods β€” but Astro only executes the method you export (GET, POST, etc.)

RouterBuilder (Catch-All & Fluent Builder)

Use RouterBuilder when you want to build routes dynamically, catch all HTTP methods via ALL, or organize routes more fluently with helpers.

const builder = new RouterBuilder();

builder
    .addGet("/ping", () => ok("pong"))
    .addPost("/submit", async ({request}) => {
        const body = await request.json();
        return ok({received: body});
    });

export const ALL = builder.build();

You can also group routes:

const users = defineGroup("/users")
    .addGet("/:id", ({params}) => ok({id: params.id}));

builder.addGroup(users);

πŸ” While .register() is still available, it's deprecated in favor of .addGroup() and .addRoute() for better structure and reusability.


πŸ” Response Helpers

Avoid boilerplate new Response(JSON.stringify(...)):

import {fileResponse} from 'astro-routify';

ok(data);                   // 200 OK
created(data);              // 201 Created
noContent();                // 204
notFound("Missing");        // 404
internalError(err);         // 500

πŸ“„ File downloads

fileResponse(content, "application/pdf", "report.pdf"); // sets Content-Type and Content-Disposition

πŸ”„ Streaming responses

Raw stream (e.g., Server-Sent Events)

stream('/clock', async ({response}) => {
    const timer = setInterval(() => {
        response.write(new Date().toISOString());
    }, 1000);

    setTimeout(() => {
        clearInterval(timer);
        response.close();
    }, 5000);
});

JSON NDStream (newline-delimited)

streamJsonND('/updates', async ({response}) => {
    response.send({step: 1});
    await delay(500);
    response.send({step: 2});
    response.close();
});

JSON Array stream

streamJsonArray('/items', async ({response}) => {
    for (let i = 0; i < 3; i++) {
        response.send({id: i});
    }
    response.close();
});

πŸ” Param Matching

Any route param like :id is extracted into ctx.params:

const builder = new RouterBuilder();

builder.addGet("/users/:id", ({params}) => ok({userId: params.id}));


//OR

defineRoute(HttpMethod.GET, "/items/:id", ({params}) => {
    return ok({itemId: params.id});
});

🀯 Why Use astro-routify?

❌ Without it

// src/pages/api/[...slug].ts
export const GET = async ({request}) => {
    const url = new URL(request.url);
    const path = url.pathname;

    if (path.startsWith('/api/users/')) {
        // Try to extract ID
        const id = path.split('/').pop();
        return new Response(JSON.stringify({id}), {
            status: 200,
            headers: {'Content-Type': 'application/json'},
        });
    }

    if (path === '/api/users') {
        return new Response(JSON.stringify([{id: 1}, {id: 2}]), {
            status: 200,
            headers: {'Content-Type': 'application/json'},
        });
    }

    if (path === '/api/ping') {
        return new Response(JSON.stringify({pong: true}), {
            status: 200,
            headers: {'Content-Type': 'application/json'}
        });
    }

    return new Response('Not Found', {status: 404});
};

πŸ“ And then there's folder hell...

src/
β”œβ”€ pages/
β”‚  β”œβ”€ api/
β”‚  β”‚  β”œβ”€ users/
β”‚  β”‚  β”‚  β”œβ”€ index.ts       // GET all users
β”‚  β”‚  β”‚  β”œβ”€ [id]/
β”‚  β”‚  β”‚  β”‚  β”œβ”€ index.ts    // GET / POST / DELETE for a user
β”‚  β”‚  β”œβ”€ ping.ts

βœ… With astro-routify

// src/pages/api/[...slug].ts

const builder = new RouterBuilder();
builder.addGet("/ping", () => ok({pong: true}));
builder.addGet("/users/:id", ({params}) => ok({userId: params.id}));

// OR

export const ALL = defineRouter([
    defineRoute(HttpMethod.GET, "/ping", () => ok({pong: true})),
    defineRoute(HttpMethod.GET, "/users/:id", ({params}) => ok({id: params.id}))
]);

πŸ“ˆ Performance

astro-routify uses a Trie structure for fast route and method matching.
It’s optimized for real-world route hierarchies, and avoids nested if chains.

πŸ§ͺ Benchmarks

Realistic and synthetic benchmarks using vitest bench.

πŸ–₯ Benchmark Machine

Tests ran on a mid-range development setup:

  • CPU: Intel Core i5-7600K @ 3.80GHz (4 cores)
  • RAM: 16 GB DDR4
  • GPU: NVIDIA GeForce GTX 1080 (8 GB)
  • OS: Windows 10 Pro 64-bit
  • Node.js: v20.x
  • Benchmark Tool: Vitest Bench

Results may vary slightly on different hardware.

πŸ”¬ Realistic route shapes (5000 registered routes):

βœ“ RouteTrie performance - realistic route shapes

 Β· Static route lookup (5000)                         1,819,681 req/sec
 Β· Param route: /users/:userId                        1,708,264 req/sec
 Β· Nested param route: /users/:id/orders/:oid         1,326,324 req/sec
 Β· Blog route: /blog/:year/:month/:slug               1,220,882 req/sec
 Β· Nonexistent path                                   1,621,934 req/sec

πŸ“ˆ Route scaling test:

βœ“ RouteTrie performance

 Β· Lookup in SMALL (100 routes)                       1,948,385 req/sec
 Β· Lookup in MEDIUM (1000 routes)                     1,877,248 req/sec
 Β· Lookup in LARGE (10000 routes)                     1,908,279 req/sec
 Β· Lookup non-existent route in LARGE                 1,962,051 req/sec

⚑ Performance stays consistently fast even with 10k+ routes


πŸ›  Designed to Scale

While focused on simplicity and speed today, astro-routify is designed to evolve β€” enabling more advanced routing patterns in the future.


πŸ“œ License

MIT β€” Β© 2025 Alex Mora


β˜• Support

If this project helps you, consider buying me a coffee. Every drop keeps the code flowing!

About

🧭 A high-performance API router for Astro, powered by a Trie-based matcher. Define clean route handlers with full HTTP method support, dynamic params, and blazing-fast lookup β€” without nested src/pages/api/ clutter.

Resources

License

Stars

Watchers

Forks

Packages

No packages published