Route structure
Each HTTP route in Gadget has two parts: a URL pattern and a handler function.
URL patterns
The URL pattern for a route in Gadget is defined by the route file's path within the routes/
directory. The name of the file should start with an HTTP verb, then have a dash, then have a valid URL segment. Requests made with that HTTP verb and segment will invoke the route handler in that file.
For example, if your gadget app is my-app.gadget.app
and you have a source file at routes/test/GET-data.js
, someone accessing https://my-app.gadget.app/test/data
in the browser would run the handler in that source file.
Example filename patterns
Route filename | Matched requests |
---|---|
routes/GET.js | GET / |
routes/GET-foo.js | GET /foo or GET /foo/ , but not GET /bar or POST /foo |
routes/GET-:id.js | GET /foo , GET /bar , GET /1 but not POST /foo or GET /foo/bar |
routes/blogs/GET-:id.js | GET /blogs/1 , GET /blogs/welcome-to-gadget |
routes/blogs/POST.js | POST /blogs , but not POST /blogs/1 |
routes/category/:category/blogs/GET-:id.js | GET /category/cooking/blogs/1 or GET /category/baking/blogs/5 |
Dynamic segments
If you have a segment in your URL that isn't static, such as an id to or a slug, you can capture a variable by adding a segment starting with a colon (:
). For example, routes/blog/post/GET-:id
would capture id=5
when accessing /blog/post/5
. You can then access these captured segments in request.params
when writing a route handler.
You can also capture part of a path segment, as long as it's separated by a dash. For example, routes/blog/:id/GET-post-:postId
would capture id=3, postId=10
when accessing /blog/3/post-10
. If you need a colon as part of a path segment, use two colons. For example, routes/GET-blog::post.js
would match /blog:post
.
If you need more flexibility in how parts of the path are captured, you can manually register a route through a route plugin.
All JS/TS files in the routes/
directory must be prefixed with an HTTP verb for route handlers, or prefixed with a +
for route
plugins. Any other filename under the routes/
directory will throw an error.
Root URLs
If you want to register a route for the root of your application, or a route at the root of a folder, name the file after just the HTTP verb. For example, routes/GET.js
will match requests to /
, or routes/foo/bar/POST.js
will match routes to /foo/bar
.
Available HTTP methods
Route handler functions can listen to any of the standard HTTP methods (also known as verbs) by using that HTTP method as the first part of the file name:
GET
, likeGET-list.js
POST
, likePOST-create.js
PUT
, likePUT-update.js
PATCH
, likePATCH-update.js
DELETE
HEAD
OPTIONS
Route handlers can also be prefixed with ANY
to respond to any HTTP method, like ANY-data.js
. To know which HTTP method was used to make a request, you can utilize the request.method
property.
routes/ANY-example.jsJavaScript1export default async function route({ request, reply }) {2 if (request.method == "GET") {3 reply.send("hello world");4 } else if (request.method == "POST") {5 reply.send("updated: " + request.body);6 } else {7 reply.code(404).send("not found");8 }9}
Trailing slashes
Routes defined in the routes/
folder respond to URLs with or without trailing slashes.
For example, if you define a route at routes/foo/GET-bar.js
, requests made to /foo/bar
or /foo/bar/
will invoke this route handler.
Route handlers
Route handlers should export a single function that takes a request and a reply parameter.
Send a plain text responseJavaScriptexport default async function route({ request, reply }) {reply.send("hello world");}
Send a JSON responseJavaScriptexport default async function route({ request, reply }) {reply.send({ message: "hello world" });}
JavaScript1// Plain text response with custom status code2export default async function route({ request, reply }) {3 reply4 .code(418)5 .type("text/plain; charset=utf-8")6 .send("I can't brew you any coffee because I'm a teapot!");7}
routes/quotes/GET-:id.jsJavaScript1// Accessing a captured id argument2export default async function route({ request, reply }) {3 if (request.params.id == "1") {4 reply.send("To be or not to be");5 } else {6 reply.code(404).send("Unknown quote id: " + request.params.id);7 }8}
For more details on route handlers, see Route configuration.
Route plugins
Route plugins are files that customize your app for the associated routes. You add a route plugin by creating a file starting with a +
sign in a particular folder of routes, to which then modifies the Fastify server powering your app for all the routes in that folder (or subfolders of it). Route plugins in one folder don't modify routes in sibling folders -- only that folder and its subfolders.
Route plugins can do many different things:
- Adding Fastify plugins like
fastify-cors
,fastify-view
, ... - Decorate requests with a logged-in user for authentication
- Redirect anonymous users to a login page
- Checking user credentials for permissions to access a set of routes
- Conditionally registering routes in loops or if statements with
server.get
, ...
Route plugins are a function that looks like a regular Fastify plugin. Plugin files should export a single function that receives a Fastify server instance as an argument.
1// in routes/+views.js2import FastifyView from "point-of-view";3export default async function (server) {4 await server.register(FastifyView, {5 engine: {6 eta: require("eta"), // an example view engine, see https://eta.js.org/7 },8 });9}
1// in routes/+redirect.js2export default async function (server) {3 server.addHook("preHandler", async ({ request, reply }) => {4 if (request.headers["secret-value"] != "12345") {5 reply.redirect("/index");6 }7 });8}
1// in routes/+custom-routes.js2export default async function (server) {3 server.get("/example/:file(^d+).png", ({ request, reply }) => {4 reply.send({ filenameWithoutExtension: request.params.file });5 });6}
What routes do route plugins affect
Route plugins are applied to all routes in the same folder as the plugin, and all subfolders of that folder. For example, if you have a plugin in routes/admin/+auth.js
, it will affect all routes in routes/admin
and all subfolders of routes/admin
, such as routes/admin/users
and routes/admin/posts
. It will not affect routes in other folders, such as routes/public
or routes/blog
.
1routes/2 +plugins.js // will affect all routes in any folder3 admin/4 +auth.js // will only affect routes in the admin folder5 GET.js6 GET-posts.js7 users/8 +customizations.js // will only affect the one route below, and nothing in the routes/admin folder9 GET.js10 posts/11 +images.js // will only affect these posts routes in routes/posts12 GET.js13 GET-:id.js
Boot plugins
Boot plugins are server customizations that affect the whole server instead of just a part. They are registered before any other route plugins or the routes themselves. Boot plugins live in the root boot/
folder. The primary use case is to register some global state, such as creating an API client of an external service. It isn't necessary to export anything from a boot plugin, but the only export supported is a function that will take a server instance.
1// in boot/errors.js2export default async function (server) {3 server.setNotFoundHandler(async ({ request, reply }) => {4 reply.send(`couldnt find ${request.path}`);5 });6 server.setErrorHandler(async (error, request, reply) => {7 reply.send(`something failed: ${error.message}`);8 });9}
Boot plugins can also be used for setting up other global server-side objects, like error handlers or persistent connections to third-party services.