fsrouter
|
📮 A file system based router for Deno. Supports Deno Deploy!
Basic usage
Given a project with the following folder structure:
my-app/
├─ pages/
│ ├─ blog/
│ │ ├─ post.ts
│ │ ├─ index.ts
│ ├─ about.ts
│ ├─ index.ts
├─ mod.ts
Each "route file" must export a FsHandler as its default export:
// my-app/pages/blog/post.ts
export default (req: Request) => {
return new Response("hello world!");
};
.js
files are fine as well:
// my-app/pages/blog/post.js
export default (req) => {
return new Response("hello world!");
};
As well as .jsx
and .tsx
files, with jsx runtime modules from whichever
source you wish:
// my-app/pages/blog/post.tsx
/** @jsx h */
import { h, renderSSR } from "https://deno.land/x/nano_jsx@v0.0.33/mod.ts";
function App() {
return (
<html>
<head>
<title>Hello from JSX</title>
</head>
<body>
<h1>Hello world</h1>
</body>
</html>
);
}
export default (_req: Request) => {
const html = renderSSR(<App />);
return new Response(html, {
headers: {
"content-type": "text/html",
},
});
};
Initialize a server by calling fsRouter
:
// my-app/mod.ts
import { fsRouter } from "https://deno.land/x/fsrouter@{VERSION}/mod.ts";
import { serve } from "https://deno.land/std@{VERSION}/http/server.ts";
// Use the file system router with base directory 'pages'
// The first argument to fsRouter requires an absolute path
// Paths starting with 'file://' are okay
serve(await fsRouter(import.meta.resolve("./pages")));
Now running:
deno run --allow-read --allow-net my-app/mod.ts
Results in routes being served as follows:
File | Route |
---|---|
pages/index.ts |
/ |
pages/about.ts |
/about |
pages/blog/index.ts |
/blog |
pages/blog/post.ts |
/blog/post |
An options object can be provided as the second argument to fsRouter
. See
RouterOptions for
details.
Dynamic routes
Dynamic routes are supported using the [slug]
syntax. This works for files,
folders, or both. For example:
File | Matches |
---|---|
pages/blog/[id].ts |
/blog/123 , /blog/first-post |
pages/[id1]/[id2].ts |
/any/route |
pages/[fallback].ts |
/caught-all , /any |
Matching slug values are provided as the second argument to FsHandler
. Given
the files as defined in the table above, the route /any/route
will be provided
a slug object of the shape { id1: 'any', id2: 'route' }
:
// my-app/pages/[id1]/[id2].ts
import { type Slugs } from "https://deno.land/x/fsrouter@{VERSION}/mod.ts";
// req url: /any/route
export default (req: Request, slugs: Slugs) => {
console.log(slugs.id1); // 'any'
console.log(slugs.id2); // 'route'
return new Response("Matched dynamic route!");
};
Typed dynamic routes
Slugs can optionally include a :string
or :number
postfix to exclusively
match strings and numbers respectively. For example:
File | Matches |
---|---|
pages/blog/[id:number].ts |
/blog/123 , /blog/45 |
pages/blog/[id:string].ts |
/blog/first-post , /blog/second-post |
Matches for slugs of type :number
will be automatically converted to type
number
:
// my-app/pages/blog/[id:number].ts
import { type Slugs } from "https://deno.land/x/fsrouter@{VERSION}/mod.ts";
// req url: /blog/123
export default (req: Request, slugs: Slugs) => {
console.log(typeof slugs.id); // 'number'
return new Response("Matched dynamic route!");
};
This automatic conversion behaviour can be disabled via RouterOptions.convertToNumber.
Watch mode
During development, you can use Deno's built-in --watch=<folder>
to restart
the server on changes. Providing a bare --watch
has the caveat of not being
able to detect new file additions, since by default Deno will watch only files
it can statically discover. By providing a root directory, Deno will be able to
detect new file additions as well:
deno run --allow-read --allow-net --watch=pages my-app/mod.ts
Permissions
Using fsrouter
requires both --allow-read
and --allow-net
for the
following reasons:
--allow-read
:fsrouter
needs to traverse the filesystem in order to discover handler files--allow-net
:fsrouter
itself doesn't actually need network access, but since it's very likely your script will include usingfsrouter
in tandem with some sort of file server, you'll likely need this permission grant
When deploying to Deno Deploy, --allow-write
is also required so fsrouter
can generate a manifest file containing static imports.
Deno Deploy
When running locally with the Deno CLI, this module uses dynamic imports to resolve file names to their respective routes. As Deno Deploy does not support dynamic imports, a "manifest" file containing static imports for every route must be generated during development and committed to your linked repository. This is the same approach taken by Fresh, and is enabled by default.
If you do not need to run your code in Deno Deploy, you can disable manifest generation with RouterOptions.generateManifest.