# 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](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), 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 | | --- | --- | | `api/routes/GET.js` | `GET /` | | `api/routes/GET-foo.js` | `GET /foo` or `GET /foo/`, but not `GET /bar` or `POST /foo` | | `api/routes/GET-[id].js` | `GET /foo`, `GET /bar`, `GET /1` but not `POST /foo` or `GET /foo/bar` | | `api/routes/blogs/GET-[id].js` | `GET /blogs/1`, `GET /blogs/welcome-to-gadget` | | `api/routes/blogs/POST.js` | `POST /blogs`, but not `POST /blogs/1` | | `api/routes/category/[category]/blogs/GET-[id].js` | `GET /category/cooking/blogs/1` or `GET /category/baking/blogs/5` | | `api/routes/repos/GET-[...].js` | `GET /repos/foo` or `GET /repos/foo/bar/baz` | ### 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 wrapped in square brackets. For example, `routes/blog/post/GET-[id].js` would capture `id=5` when accessing `/blog/post/5`. You can then access these captured segments in `request.params` when writing a . ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Params: { id: string } }> = async ({ request, reply, }) => { if (request.params.id == "1") { await reply.send("To be or not to be"); } else { await reply.code(404).send("Unknown quote id: " + request.params.id); } }; export default route; ``` 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 more flexibility in how parts of the path are captured, you can manually register a route through a . 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. ### Wildcard segments  If you have a portion of your URL that should match any valid URL segment, including slashes, you can capture it with a wildcard segment using `[...]`. For example, `routes/blog/post/GET-[...].js` will match any request to `/blog/post/foo`, or `/blog/post/foo/bar`, or any longer segment as well. Wildcards are useful for catchall redirects, 404 pages, or routing schemes that include slashes. You can access the captured wildcard value in `request.params['*']` when writing a . ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Params: { "*": string } }> = async ({ request, reply, }) => { if (request.params["*"] == "foo/bar") { await reply.send("the foobar post"); } else { await reply.code(404).send("Unknown post URL: " + request.params["*"]); } }; export default route; ``` ### 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`, like `GET-list.js` * `POST`, like `POST-create.js` * `PUT`, like `PUT-update.js` * `PATCH`, like `PATCH-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`](https://www.fastify.dev/docs/latest/Reference/Request/) property. ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply }) => { if (request.method == "GET") { await reply.send("hello world"); } else if (request.method == "POST") { await reply.send("updated: " + request.body); } else { await reply.code(404).send("not found"); } }; export default route; ``` ### 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](https://www.fastify.dev/docs/latest/Reference/Request/) and a [reply](https://www.fastify.dev/docs/latest/Reference/Reply/) parameter. ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply }) => { await reply.send("hello world"); }; export default route; ``` ```typescript import type { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply }) => { await reply.send({ message: "hello world" }); }; export default route; ``` ```typescript // Plain text response with custom status code import type { RouteHandler } from "gadget-server"; const route: RouteHandler = async ({ request, reply }) => { await reply .code(418) .type("text/plain; charset=utf-8") .send("I can't brew you any coffee because I'm a teapot!"); }; export default route; ``` ```typescript // Accessing a captured id argument import type { RouteHandler } from "gadget-server"; const route: RouteHandler<{ Params: { id: string } }> = async ({ request, reply, }) => { if (request.params.id == "1") { await reply.send("To be or not to be"); } else { await reply.code(404).send("Unknown quote id: " + request.params.id); } }; export default route; ``` For more details on route handlers, see [Route configuration](https://docs.gadget.dev/guides/http-routes/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`](https://www.npmjs.com/package/fastify-cors), [`fastify-view`](https://www.npmjs.com/package/point-of-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](https://www.fastify.dev/docs/latest/Guides/Plugins-Guide/). Plugin files should export a single function that receives a [Fastify server](https://www.fastify.dev/docs/latest/Reference/Server/#instance) instance as an argument. ```typescript // in routes/+views.ts import FastifyView from "point-of-view"; import type { Server } from "gadget-server"; export default async function (server: Server) { await server.register(FastifyView, { engine: { // an example view engine, see https://eta.js.org/ eta: require("eta"), }, }); } ``` ```typescript // in routes/+redirect.ts import type { Server } from "gadget-server"; export default async function (server: Server) { server.addHook("preHandler", async ({ request, reply }) => { if (request.headers["secret-value"] != "12345") { await reply.redirect("/index"); } }); } ``` ```typescript // in routes/+custom-routes.ts import type { Server } from "gadget-server"; export default async function (server: Server) { server.get("/example/:file(^d+).png", async ({ request, reply }) => { await reply.send({ filenameWithoutExtension: request.params.file }); }); } ``` ### 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`. ```markdown routes/ +plugins.js // will affect all routes in any folder admin/ +auth.js // will only affect routes in the admin folder GET.js GET-posts.js users/ +customizations.js // will only affect the one route below, and nothing in the routes/admin folder GET.js posts/ +images.js // will only affect these posts routes in routes/posts GET.js 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 should live in the root `api/boot` folder (otherwise they will not work as expected). 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. ```typescript // in api/boot/errors.ts import type { Server } from "gadget-server"; export default async function (server: Server) { server.setNotFoundHandler(async ({ request, reply }) => { await reply.send(`couldn't find ${request.path}`); }); server.setErrorHandler(async (error, request, reply) => { await reply.send(`something failed: ${error.message}`); }); } ``` Boot plugins can also be used for setting up other global server-side objects, like error handlers or persistent connections to third-party services. ## Deprecated route filename syntax  Gadget supported a previous version of the route filename syntax that isn't compatible with Windows users as it uses filenames with illegal characters on Windows. This route syntax still works, but we recommend moving to the new square-bracket based syntax. Each old route filename has a corresponding new syntax: | Name | Old syntax | New syntax | Old example | New Example | | --- | --- | --- | --- | --- | | Single dynamic segment | `:name` | `[name]` | `routes/GET-:foo.js` | `routes/GET-[foo].js` | | Optional dynamic segment | `:name?` | `[[name]]` | `routes/GET-:foo?.js` | `routes/GET-[[foo]].js` | | Folder dynamic segment | `:name` | `[name]` | `routes/:foo/GET-bar.js` | `routes/[foo]/GET-bar.js` | | Wildcard dynamic segment | `*` | `[...]` | `routes/foo/GET-*.js` | `routes/foo/GET-[...].js` | If you encounter any errors with the square bracket based syntax, please get in touch with the Gadget staff on [Discord](https://ggt.link/discord).