Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/yuhr/routets
Vanilla filesystem-based routing for Deno.
https://github.com/yuhr/routets
deno http-router http-routing http-server typescript web-framework
Last synced: about 2 months ago
JSON representation
Vanilla filesystem-based routing for Deno.
- Host: GitHub
- URL: https://github.com/yuhr/routets
- Owner: yuhr
- License: mpl-2.0
- Created: 2023-06-24T08:40:11.000Z (over 1 year ago)
- Default Branch: develop
- Last Pushed: 2024-09-08T02:47:26.000Z (5 months ago)
- Last Synced: 2024-10-15T10:26:15.417Z (3 months ago)
- Topics: deno, http-router, http-routing, http-server, typescript, web-framework
- Language: TypeScript
- Homepage: https://deno.land/x/routets
- Size: 138 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# ROUTETS
[![License](https://img.shields.io/github/license/yuhr/routets?color=%231e2327)](LICENSE)
Vanilla filesystem-based routing for Deno.
`routets` is a [`Deno.ServeHandler`](https://docs.deno.com/api/deno/~/Deno.ServeHandler) generator that performs filesystem-based routing.
No other stuff. That's all. I was always tired of fullstack frameworks such as Fresh or Aleph.js, because of the tightly coupled design that forces users to be on the rails. So I ended up making this stupid-simple solution, which is aimed to be:
- No magic and no blackbox
- Small size and less dependency
- No squatted pathnames like `/_app` or `/_404`
- No lock-in to a specific JSX implementation
- No lock-in to a specific architecture; MPA or SPA, SSR or CSR, etc.So, `routets` is deliberately less-featured. It just provides a basic building block for writing web servers in Deno, leveraging Create Your Own™ style of experience.
Notably, we use a suffix for route filenames like `*.route.ts`. This allows you to place related modules like `*.test.ts` aside of routes.
## Basic Usage
Create a file with the filename being `.route.ts`, say `./greet.route.ts` here and the content is like this:
```typescript
import Route from "https://lib.deno.dev/x/routets@v2/Route.ts"export default new Route(async () => {
return new Response("Hello, World!")
})
````routets` comes with a built-in CLI. During development, you can use this and serve your routes immediately:
```sh
$ deno install -Af https://lib.deno.dev/x/routets@v2/routets.ts
$ routets # or `routets somewhere` to serve `somewhere/greet.route.ts` at `/greet`
Listening on http://localhost:8000/
```And you'll see “Hello, World!” at [`http://localhost:8000/greet`](http://localhost:8000/greet).
Alternatively, of course you can create your own script:
```typescript
import Router from "https://lib.deno.dev/x/routets@v2/Router.ts"await Deno.serve(new Router()).finished
```## Advanced Usage
### Dynamic Routes
`routets` supports dynamic routes by [URL Pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API). Please refer to the MDN documentation for the syntax and examples.
Captured parts of the pathname will be available in the first parameter of the handler. For example, when you have `:dynamic.route.ts` with the content being:
```typescript
import Route from "https://lib.deno.dev/x/routets@v2/Route.ts"export default new Route(async ({ captured }) => {
return new Response(JSON.stringify(captured), { headers: { "Content-Type": "application/json" } })
})
```Accessing `/route` will show you `{"dynamic":"route"}`.
### Route Precedence
Once you have started using dynamic routes, you may notice it is unclear which route will be matched when multiple dynamic routes are valid for the requested pathname. For example, if you have a file named `greet.route.ts` and another file named `*.route.ts`, which one will be matched when you access `/greet`?
By default, `routets` doesn't do anything smart, and just performs codepoint-wise lexicographic ordering. So, in the above example, `*.route.ts` will be matched first, as `*` precedes `g` in Unicode. If you want to change this behavior, just named-export a number as `precedence` from each route:
```typescript
// in `*.route.ts`
export const precedence = 0
``````typescript
// in `greet.route.ts`
export const precedence = 9
```Routes with greater precedences are matched first. Think of it as `z-index` in CSS. So, this time `greet.route.ts` will be matched first.
If `precedence` is not exported, it implies 0.
### Route Fallthrough
If a route returns nothing (namely `undefined`), then it fallthroughs to the next matching route.
### Extending `Route`
If you want to insert middlewares before/after an execution of handlers, you can extend the `Route` class as usual in TypeScript.
To exercise this, here we add support for returning a React element from handlers!
```typescript
import Route from "https://lib.deno.dev/x/routets@v2/Route.ts"
import { renderToReadableStream } from "https://esm.sh/[email protected]/server"
import { type ReactElement, Suspense } from "https://esm.sh/[email protected]"class RouteReact extends Route {
constructor(handler: Route.Handler>) {
super(async context => {
const response = await handler(context)
return new Response(
await renderToReadableStream(
Loading...}>{response}
,
),
{ headers: { "Content-Type": "text/html" } },
)
})
}
}export default RouteReact
```And don't forget to add following options to your `deno.json`:
```jsonc
{
"compilerOptions": {
"jsx": "react-jsx",
"jsxImportSource": "https://esm.sh/[email protected]"
}
}
```That's it! You can now create a route using it, e.g. with the filename being `.route.tsx`:
```typescript
import RouteReact from "./RouteReact.ts"
import { delay } from "https://deno.land/[email protected]/async/delay.ts"let done = false
const Component = () => {
if (!done) {
throw delay(3000).then(() => (done = true))
} else {
done = false
return Hello, World!
}
}export default new RouteReact(async () => {
return
})
```In a browser, accessing [`http://localhost:8000`](http://localhost:8000) will show you “Loading…” for 3 seconds, and then “Hello, World!”.
### Changing Suffix
Changing the route filename suffix (`route` by default) is possible by `--suffix` when using the CLI and by `suffix` option when using the `Router` constructor. Although, there are some restrictions on the shape of suffixes:
- Cannot be empty
- Cannot contain slashes
- Cannot start or end with dotsThese are by design and will never be lifted.
## Deploying to Deno Deploy
`routets` uses dynamic imports to discover routes. This works well locally, but can be a problem if you want to get it to work with environments that don't support dynamic imports, such as [Deno Deploy](https://github.com/denoland/deploy_feedback/issues/1).
For this use case, by default the `routets` CLI and the `Router` constructor do generate a server module `serve.gen.ts` that statically imports routes. This module can directly be used as the entrypoint for Deno Deploy.
You can disable this behavior by `--no-write` option when using the CLI and by `write` option when using the `Router` constructor.
## Difference from `fsrouter`
There exists a similar package [`fsrouter`](https://deno.land/x/fsrouter) which has quite the same UX overall, but slightly different in:
- Suffix namespacing. `routets` uses namespaced filenames e.g. `greet.route.ts`, while `fsrouter` is just `greet.ts`.
- Dynamic routing syntax. `routets` uses [URL Pattern API](https://developer.mozilla.org/en-US/docs/Web/API/URL_Pattern_API) e.g. `:id.route.ts`, while `fsrouter` uses the [bracket syntax](https://github.com/justinawrey/fsrouter#dynamic-routes) e.g. `[id].ts`. Also, `routets` doesn't support [typed dynamic routes](https://github.com/justinawrey/fsrouter#typed-dynamic-routes).
- JavaScript file extensions. `routets` doesn't allow `js` or `jsx`, while `fsrouter` does.