├── README.md ├── index.js └── package.json /README.md: -------------------------------------------------------------------------------- 1 | # astro-router 2 | 3 | At the time of writing, Astro SSR does not yet support Middleware. This project is just a simple router that adds middleware to your Astro SSR projects, as well as some utilities like getting params, query params, headers, etc. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm i -S astro-router 9 | ``` 10 | 11 | ## Usage 12 | 13 | Import the `router`: 14 | ```js 15 | import { router } from 'astro-router'; 16 | ``` 17 | 18 | Then export your `get`, `post`, etc: 19 | `/foo/[...all]/index.js`: 20 | ```js 21 | export const get = router({ 22 | routes: [ 23 | { 24 | path: '/foo', 25 | response: () => new Response(null, {status: 200}) 26 | } 27 | ] 28 | }); 29 | ``` 30 | 31 | ## Examples 32 | 33 | `/sales/[...all]/index.js`: 34 | ```js 35 | import { router } from 'astro-router'; 36 | import { auth, logger } from './middleware.js'; 37 | import { User, Order } from './db.js'; 38 | 39 | export const get = router({ 40 | routes: [ 41 | { 42 | path: '/sales/:user/:order', 43 | middleware: [logger, auth], 44 | response({params}) { 45 | const user = await User.findOne({id: params.user}); 46 | const order = await Order.findOne({id: params.order}); 47 | 48 | return new Response(null, {status: 200}); 49 | } 50 | } 51 | ] 52 | }) 53 | ``` 54 | 55 | `Request: /users/1234?foo=bar`: 56 | ```js 57 | export const get = router({ 58 | path: '/users/:id', 59 | middleware: [logger, auth], 60 | response: ({params, query}) => { 61 | console.log(params.id) // '1234' 62 | console.log(query.foo) // 'bar' 63 | } 64 | }); 65 | ``` 66 | 67 | ## Api 68 | 69 | ### Router 70 | 71 | ```js 72 | export const get = router({ 73 | /** Routes */ 74 | routes: [ 75 | { 76 | /** The path to match, supports 'express'-style route params */ 77 | path: '/users/:id', 78 | middleware: [ 79 | ({ 80 | /** Astro's original `request` object */ 81 | request, 82 | /** Any query params as object */ 83 | query, 84 | /** Route params as object */ 85 | params, 86 | /** Headers as object */ 87 | headers 88 | /** Url object */ 89 | url, 90 | /** Next middleware to invoke */ 91 | next 92 | }) => { 93 | next(); 94 | } 95 | ], 96 | response: ({ 97 | /** Astro's original `request` object */ 98 | request, 99 | /** Any query params as object */ 100 | query, 101 | /** Route params as object */ 102 | params, 103 | /** Headers as object */ 104 | headers 105 | /** Url object */ 106 | url, 107 | }) => { 108 | return new Response(null, {status: 200}) 109 | } 110 | } 111 | ], 112 | /** 113 | * Custom fallback in case no routes match 114 | * defaults to a 404 response 115 | */ 116 | fallback: () => new Response(null, {status: 404}), 117 | }) 118 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | async function invokeMiddleware(context) { 2 | if (!context.middleware.length) return; 3 | 4 | const mw = context.middleware[0]; 5 | 6 | return mw({ 7 | ...context, 8 | next: async () => { 9 | await invokeMiddleware({ 10 | ...context, 11 | middleware: context.middleware.slice(1) 12 | }); 13 | } 14 | }); 15 | } 16 | 17 | const DEFAULT_FALLBACK = new Response(null, {status: 404}); 18 | 19 | function initRoutes(routes) { 20 | return routes.map(({ 21 | response, 22 | middleware, 23 | path 24 | }) => ({ 25 | response: response, 26 | middleware: middleware ?? [], 27 | pattern: new URLPattern({ 28 | pathname: path, 29 | search: '*', 30 | hash: '*' 31 | }) 32 | })) 33 | } 34 | 35 | function matchRoute(url, routes) { 36 | for (const route of routes) { 37 | const match = route.pattern.exec(url); 38 | if(match) { 39 | return { 40 | ...route, 41 | params: match?.pathname?.groups ?? {}, 42 | }; 43 | } 44 | } 45 | } 46 | 47 | const objectify = (acc, [k,v]) => ({...acc, [k]: v}); 48 | 49 | export function router({fallback, routes}) { 50 | const r = initRoutes(routes); 51 | 52 | return async (_, request) => { 53 | const url = new URL(request.url); 54 | const query = [...url.searchParams.entries()].reduce(objectify, {}) ?? {}; 55 | const headers = [...request.headers.entries()].reduce(objectify, {}) ?? {}; 56 | 57 | const route = matchRoute(url, r); 58 | const { params, middleware } = route; 59 | const context = {request, query, params, url, headers}; 60 | 61 | if(route) { 62 | await invokeMiddleware({...context, middleware}); 63 | return await route.response(context); 64 | } else { 65 | return fallback ?? DEFAULT_FALLBACK; 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "astro-router", 3 | "version": "0.0.1", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC" 13 | } 14 | --------------------------------------------------------------------------------