HTTP routes accept a wide variety of options to change route behavior. Route options are set for a route by setting options on the route handler which must be the default export from your file:
6// add route options like \`schema\`, \`errorHandler\`, etc here. for example, we validate the query string has a name key
7schema:{
8querystring:{
9type:"object",
10properties:{
11name:{type:"string"},
12},
13required:["name"],
14},
15},
16};
Gadget routes are built on top of Fastify, which means your route's options can make use of all the same options as Fastify routes. For a full list of options, see the Fastify route options documentation.
Route options
schema: an object containing the schemas for the request and response. They
need to be in JSON Schema format, check
fastify's docs for more info.
body: validates the body of the request if it is a POST, PUT, or PATCH
method.
querystring or query: validates the querystring. This can be a complete
JSON Schema object, with the property type of object and properties
object of parameters, or simply the values of what would be contained in the
properties object as shown below.
params: validates the params.
response: filter and generate a schema for the response, setting a schema
allows us to have 10-20% more throughput.
exposeHeadRoute: creates a sibling HEAD route for any GET routes.
Defaults to the value of exposeHeadRoutes
instance option. If you want a custom HEAD handler without disabling this
option, make sure to define it before the GET route.
attachValidation: attach validationError to request, if there is a schema
validation error, instead of sending the error to the error handler.
The default error format is the Ajv one.
onRequest(request, reply, done): a function called as soon
as a request is received, it could also be an array of functions.
preParsing(request, reply, done): a function called
before parsing the request, it could also be an array of functions.
preValidation(request, reply, done): a function
called after the shared preValidation hooks, useful if you need to perform
authentication at route level for example, it could also be an array of
functions.
preHandler(request, reply, done): a function called
just before the request handler, it could also be an array of functions.
preSerialization(request, reply, payload, done): a
function called just before the serialization,
it could also be an array of functions.
onSend(request, reply, payload, done): a function
called right before a response is sent, it could also be an array of
functions.
onResponse(request, reply, done): a function called
when a response has been sent, so you will not be able to send more data to
the client. It could also be an array of functions.
onTimeout(request, reply, done): a function called
when a request is timed out and the HTTP socket has been hanged up.
onError(request, reply, error, done): a function
called when an Error is thrown or send to the client by the route handler.
handler({ request, reply }): the function that will handle this request. The
Fastify server will be bound to this when the handler is
called. Note: using an arrow function will break the binding of this.
errorHandler(error, request, reply): a custom error handler for the scope of
the request. Overrides the default error global handler, and anything set by
setErrorHandler in plugins, for requests to the route.
To access the default handler, you can access instance.errorHandler. Note
that this will point to fastify's default errorHandler only if a plugin
hasn't overridden it already.
validatorCompiler({ schema, method, url, httpPart }): function that builds
schemas for request validations. See the Validation and
Serialization
documentation.
serializerCompiler({ { schema, method, url, httpStatus } }): function that
builds schemas for response serialization. See the Validation and
Serialization
documentation.
schemaErrorFormatter(errors, dataVar): function that formats the errors from
the validation compiler. See the Validation and
Serialization
documentation. Overrides the global schema error formatter handler, and
anything set by setSchemaErrorFormatter, for requests to the route.
bodyLimit: prevents the default JSON body parser from parsing request bodies
larger than this number of bytes. Must be an integer. You may also set this
option globally when first creating the Fastify instance with
fastify(options). Defaults to 1048576 (1 MiB).
logLevel: set log level for this route.
logSerializers: set serializers to log for this route.
config: object used to store custom configuration.
prefixTrailingSlash: string used to determine how to handle passing / as a
route with a prefix.
both (default): Will register both /prefix and /prefix/.
slash: Will register only /prefix/.
no-slash: Will register only /prefix.
See Fastify's docs for exhaustive documentation on these route options.
Route context
The request context parameter provides many of the same context properties that are available in actions.
Context key
Description
request
the Request object describing the incoming HTTP request
a connected, authorized instance of the generated API client for the current Gadget application. See the API Reference for more details on this object's interface.
applicationSession
a record representing the current user's session, if there is one.
applicationSessionID
the ID of the record representing the current user's session, if there is one.
connections
an object containing client objects for all connections. Read the connections guide to see what each connection provides.
logger
a logger object suitable for emitting log entries viewable in Gadget's Log Viewer.
config
an object of all the environment variables created in Gadget's Environment Variables editor.
currentAppUrl
the current url for the environment. e.g. https://my-app.gadget.app
For example, we can use the api object to make API calls to your application's API, and return them as JSON:
routes/GET-example.js
JavaScript
exportdefaultasyncfunctionroute({ request, reply, api }){
const records =await api.someModel.findMany();
await reply.code(200).send({result: records });
}
We can use the logger object to emit log statements about our request:
The connections object passed to each route handler contains a client object for each connection. For example, we can make an API call for a particular Shopify shop:
The request object passed in the route context describes the incoming HTTP request, with properties for accessing the HTTP request headers, the request body, the matched route, and more. request is powered by Fastify, a high-performance HTTP framework for nodejs.
Request objects have the following fields:
FastifyRequest field
Description
query
the parsed query string from the incoming request, its format is specified by the route's querystringParser
body
the request payload, see Content-Type Parser for details on what request payloads Fastify natively parses and how to support other content types
params
the params matching the URL
headers
the headers getter and setter
raw
the incoming HTTP request from Node core
id
the request ID
log
a logger instance for the incoming request
ip
the IP address of the incoming request
hostname
the host of the incoming request (derived from X-Forwarded-Host header when the trustProxy option is enabled). For HTTP/2 compatibility it returns :authority if no host header exists.
protocol
the protocol of the incoming request (will always be https on Gadget)
method
the method of the incoming request
url
the URL of the incoming request
routerMethod
the method defined for the router that is handling the request
routerPath
the path pattern defined for the router that is handling the request
is404
true if the request is being handled by a 404 error handler, false if it is not
socket
the underlying connection of the incoming request
routeSchema
the scheme definition set for the router that is handling the request
routeConfig
the route config object
routeOptions
the route option object passed when defining the route
bodyLimit
either the server-wide limit or route-specific limit on the size of the request body
method
the HTTP method for the route, like GET, POST, or DELETE
url
the path of the URL to match this route
logLevel
log level defined for this route
version
a semver-compatible string that defines the version of the endpoint
exposeHeadRoute
creates a sibling HEAD route for any GET routes
.prefixTrailingSlash
string used to determine how to handle passing / as a route with a prefix.
The reply object passed to each route in the context has functions for setting up and sending an HTTP response from your server for your route. The object is a FastifyReply object from Fastify, a high-performance HTTP framework for nodejs.
Reply objects have these functions and properties:
Reply property
Description
code(statusCode)
sets the status code
status(statusCode)
an alias for .code(statusCode)
statusCode
read and set the HTTP status code
header(name, value)
sets a response header
headers(object)
sets all the keys of the object as response headers
getHeader(name)
retrieve the value of an already set header
getHeaders()
gets a shallow copy of all current response headers
removeHeader(key)
remove the value of a previously set header
hasHeader(name)
determine if a header has been set
trailer(key, function)
sets a response trailer
hasTrailer(key)
determine if a trailer has been set
removeTrailer(key)
remove the value of a previously set trailer
type(value)
sets the header Content-Type
redirect([code,] dest)
redirect to the specified URL with an optional status code. If not provided, the status code defaults to 302
callNotFound()
invokes the custom not found handler
serialize(payload)
serializes the specified payload using the default JSON serializer or using the custom serializer (if one is set) and returns the serialized payload
serializer(function)
sets a custom serializer for the payload
send(payload)
sends the payload to the user, could be a plain text, a buffer, JSON, stream, or an Error object
sent
a boolean value that you can use if you need to know if send has already been called
The .send() function on the FastifyReply object sends a response to the requesting client. .send() accepts a variety of datatypes and behaves differently depending on what is passed:
Sending objects
Calling .send() with an object will send the object as a JSON response. If your route has an output schema defined using route.options.schema, that schema will be used for a performant JSON serialization of the object, and otherwise JSON.stringify() will be used.
Calling .send() with a string behaves differently if you have set the value of the Content-Type header or not.
if Content-Type is not set, Gadget will send the string as a raw response right to the client, without any intermediate serialization or deserialization.
if Content-Type is set to application/json, Gadget will send the string as is, expecting it to contain valid JSON
if Content-Type is set, Gadget will attempt to serialize the string using the content type parser assigned to the set content type. If a custom content type serializer hasn't been set, the string will be sent unmodified
Gadget supports sending response streams to the browser, instead of sending the response all at once. This allows long-running responses that send data as it is available, subscribe to upstream datastreams, or do anything else that produces data over time.
To send a streaming response, call reply.send with a nodejs ReadableStream object. Readable streams can be created by reading files, remote resources like proxied HTTP requests, etc. If you are sending a stream and you have not set a Content-Type header, send will set it to application/octet-stream.
14// push the current time to the stream every second for 10 seconds
15let counter =0;
16while(counter++<10){
17 stream.push(`${newDate()}\n`);
18awaitsetTimeout(1000);
19}
20
21// end the stream
22 stream.push(null);
23}
If you have a stream of some other sort like an event emitter or a function that calls a callback, you must convert it to a Readable stream object before sending it to get a streaming response.
Streaming OpenAI chat completions
Gadget has built in support for replying with a streaming response from OpenAI's node client (version 4 or higher) which is part of the connections object when the OpenAI connection is installed.
Calling .send() with a buffer will send the raw bytes in the buffer to the client. If you haven't already set a Content-Type header, Gadget will set it to application/octet-stream before sending the response.
Gadget supports caching your HTTP route responses within a high-performance CDN. This is useful for giving the best experience to your users by serving requests at the edge close to them, and for avoiding re-generating expensive responses too often.
Caching with Gadget is driven entirely by the standard Cache-Control HTTP response header, which you can set with the reply.header or reply.headers function.
For example, you can set the content of an HTTP route to be cached for 1 hour with the following:
JavaScript
exportdefaultasyncfunction({ request, reply }){
await reply
.header("Cache-Control","public, max-age=3600")
.send("this response will be cached for 1 hour");
}
For more information on valid Cache-Control header values, see the MDN docs.
Caching for a duration of time
Set the Cache-Control header to public, max-age=<seconds> to have Gadget's CDN cache the content for the specified number of seconds. For example, to cache a response for 1 hour, you can set the header to public, max-age=3600, or to cache for one day, you can set the header to public, max-age=86400.
JavaScript
exportdefaultasyncfunction({ request, reply }){
await reply
.header("Cache-Control","public, max-age=3600")
.send("this will be cached for 1 hour");
}
Caching forever
Set the Cache-Control header to public, immutable, max-age=31536000 to have Gadget's CDN cache the content for as long as possible. This is appropriate only for content that you know won't change, or where it is ok for some clients to have permanently stale data.
.send("this will be cached for as long as possible in the CDN and in browsers");
}
Preventing caching
Caching can be explicitly disabled by setting the Cache-Control header to private, no-store. This is appropriate for content that is sensitive or that you don't want cached for any reason. Generally, browsers won't cache data if there is no Cache-Control header present at all, but you can add this header to make it explicit.
JavaScript
exportdefaultasyncfunction({ request, reply }){
await reply
.header("Cache-Control","private, no-store")
.send("this will never be cached by the CDN or browser");
}
Asking browsers to revalidate
The Cache-Control header can contain instructions to browsers allowing them to serve stale content while re-fetching up-to-date data in the background.
Set the Cache-Control header to max-age=1, stale-while-revalidate=59 to have Gadget's CDN cache the content for one minute while instructing the browser and CDN to refetch the content if it is older than one second. This is appropriate for data that is changing somewhat often, but where you would rather users see somewhat stale data and save request processing time.
5"this will be served to users up to one minute out of date, and re-fetched in the background."
6);
7}
For more information on the stale-while-revalidate directive, see this introduction or the MDN docs.
Default HTTP route caching behavior
By default, HTTP route contents are never cached unless you set the Cache-Control or CDN-Cache-Control header. If you set either of those headers, caching is enabled.
API response caching
Your app's GraphQL API is not cached at an HTTP layer so clients are never served out-of-date data. Gadget implements caching within your GraphQL backend to make your responses fast while remaining fresh.
Currently, you can't have your API responses set custom Cache-Control headers. If you want to create a CDN-cached response or browser-cached response for API data, you must use an HTTP route to fetch data from the API, and serve it with headers of your choosing.
For example, we can request a list of the most recent records of the post model from an app's GraphQL API in an HTTP route, and serve a cached response there:
routes/GET-recent-posts.js
JavaScript
1exportdefaultasyncfunction({ request, reply, api }){
If you set a Cache-Control or CDN-Cache-Control header, Gadget's CDN and/or users' browsers will cache your content for the duration you specify in the header. There isn't a way to explicitly expire this cache in users browsers or within Gadget's CDN. Please get in touch with the Gadget team on Discord if you'd like to explore custom cache expiry solutions.
CORS configuration
Gadget applications often serve requests made from domains other than your-app.gadget.app which makes them cross-domain and governed by browser Cross Origin Resource Sharing (CORS) rules. If you're making requests to Gadget from a Next.js app hosted on another domain, a Shopify storefront, or anywhere other than your-app.gadget.app, you need to make sure you have configured your app to respond to CORS requests correctly.
To set up CORS headers for your app, we suggest using @fastify/cors. @fastify/cors is a high-quality plugin for handling CORS requests from the Fastify ecosystem available on npm.
To start, open your Gadget command palette ( P or Ctrl P) and enter yarn add @fastify/cors. It will be added to your project's package.json file.
You can also manually add @fastify/cors to your package.json and click Run yarn to install it:
package.json
json
1{
2"dependencies":{
3"@fastify/cors":"^8.5.0"
4// ...
5}
6}
After, you can mount @fastify/cors in your application with a Route plugin file at routes/+scope.js:
routes/+scope.js route plugin file
JavaScript
1importcorsfrom"@fastify/cors";
2exportdefaultasyncfunction(server){
3await server.register(cors,{
4// allow CORS requests from my-cool-frontend.com
5origin:["https://my-cool-frontend.com"],
6// allow GET, POST, and PUT requests
7methods:["GET","POST","PUT"],
8// other options, see here: https://www.npmjs.com/package/fastify-cors
9});
10}
When your application boots, this route plugin will be required, and configure @fastify/cors to send the right Access-Control-Allow-Origin header to browsers.
Just like all route plugins, +scope.js files that mount @fastify/cors will only affect requests to route contained in the same
folder/subfolder as the +scope.js file. Registering @fastify/cors directly in routes/+scope.js will affect all the routes of your
app, or registering it in routes/api/+scope.js will only affect requests to any route in that routes/api folder. This allows different
CORS configurations for different parts of your application.
On Gadget framework v0.1 or v0.2?
If your Gadget app is on framework version v0.1 or v0.2, you'll need to install fastify-cors instead of @fastify/cors. You can do this by manually adding it to your package.json file and clicking Run yarn:
package.json
json
1{
2"dependencies":{
3"fastify-cors":"6.0.3"
4// ...
5}
6}
Allowing requests from any domain
If your app is intended for any domain, you can configure @fastify/cors to allow any origin to make requests to your app with origin: true.
routes/+scope.js route plugin file
JavaScript
1importcorsfrom"@fastify/cors";
2exportdefaultasyncfunction(server){
3await server.register(cors,{
4origin:true,// allow requests from any domain
5});
6}
Fine-grained CORS configuration
If you want to avoid setting CORS headers for your entire application, you can also manually manage CORS headers using reply.header in a route file, or add OPTIONS-*.js routes for handling CORS preflight requests yourself.
For example, we can reply with a base set of CORS headers right in a route file:
10// send no response to meet the preflight request spec
11.send("")
12.code(200);
13}
Note that if using @fastify/cors, you don't need to add manual CORS header setting or explicit OPTIONS-*.js routes as @fastify/cors handles this for you.
Multipart requests
Gadget applications don't support requests that use Content-Type: multipart/form-data (multipart requests) by default, but you can add multipart support using the @fastify/multipart package from npm.
If you're processing file uploads, you can utilize Gadget's built-in support for file storage and uploading using the
file
field type. See the Storing Files guide for more information.
First, you need to install @fastify/multipart from npm. Open your Gadget command palette ( P or Ctrl P) and enter yarn add @fastify/multipart. It will be added to your project's package.json file.
You can also manually add @fastify/multipart to your package.json and click Run yarn to install it:
package.json
json
{
"dependencies":{
"@fastify/multipart":"^8.0.0"
}
}
Next, you need to mount @fastify/multipart into your server with a boot or route plugin. A boot plugin will register it globally, and a route plugin will register it for only some routes. To keep things simple let's demonstrate with a boot plugin:
boot/multipart.js
JavaScript
importFastifyMultipartfrom"@fastify/multipart";
exportdefaultasyncfunction(server){
await server.register(FastifyMultipart);
}
With the plugin registered, you can access POSTed file data using request.file or request.files in your routes:
If your Gadget app is on framework version v0.1 or v0.2, you'll need to install fastify-multipart instead of @fastify/multipart. You can do this by manually adding it to your package.json file and clicking Run yarn: