Unlike Next.js, your api/
folder should be a sibling of pages/
instead
of being nested inside. But pages/api
is still supported for
compatibility with Next.js.
Any file inside an api/
folder are accessible at a URL corresponding to
its path inside api/
. So app/projects/api/webhook.ts
will be at
localhost:3000/api/webhook
.
For example, the following API route app/api/hello.js
handles a json
response:
const handler = (req: BlitzApiRequest, res: BlitzApiResponse) => {
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ name: "John Doe" }))
}
export default handler
For an API route to work, you need to export as default a function (a.k.a request handler), which then receives the following parameters:
req: BlitzApiRequest
: An instance of
http.IncomingMessage,
plus some pre-built middlewares you can see hereres: BlitzApiResponse
: An instance of
http.ServerResponse,
plus some helper functions you can see hereIf you want to avoid writing the types BlitzApiRequest
and
BlitzApiResponse
everywhere, you can use BlitzApiHandler
:
const handler: BlitzApiHandler = (req, res) => {
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ name: "John Doe" }))
}
export default handler
To handle different HTTP methods in an API route, you can use req.method
in your request handler, like so:
const handler: BlitzApiHandler = (req, res) => {
if (req.method === "POST") {
// Process a POST request
} else {
// Handle any other HTTP method
}
}
export default handler
To fetch API endpoints, take a look into any of the examples at the start of this section.
API Routes do not specify CORS headers, meaning they are same-origin only by default. You can customize such behavior by wrapping the request handler with the cors middleware.
API Routes do not increase your client-side bundle size. They are server-side only bundles.
You can use the getSession
function to get the session of the user. Here
is an example using session in a API route app/api/customRoute.tsx
:
import { getSession, BlitzApiRequest, BlitzApiResponse } from "blitz"
export default async function customRoute(
req: BlitzApiRequest,
res: BlitzApiResponse
) {
const session = await getSession(req, res)
console.log("User ID:", session.userId)
res.statusCode = 200
res.setHeader("Content-Type", "application/json")
res.end(JSON.stringify({ userId: session.userId }))
}
This is called in the frontend like this:
import { getAntiCSRFToken } from "blitz"
const antiCSRFToken = getAntiCSRFToken()
const response = await window.fetch("/api/customRoute", {
credentials: "include",
headers: {
"anti-csrf": antiCSRFToken,
},
})
Read more about Session Management
Dynamic API routes follow the same file naming rules used for pages
.
For example, the API route app/api/post/[pid].js
has the following code:
const handler: BlitzApiHandler = (req, res) => {
const {
query: { pid },
} = req
res.end(`Post: ${pid}`)
}
export default handler
Now, a request to /api/post/abc
will respond with the text: Post: abc
.
A very common RESTful pattern is to set up routes like this:
GET api/posts/
- gets a list of posts, probably paginatedGET api/posts/12345
- gets post id 12345We can model this in two ways:
Option 1:
/api/posts.js
/api/posts/[postId].js
Option 2:
/api/posts/index.js
/api/posts/[postId].js
Both are equivalent.
API Routes can be extended to catch all paths by adding three dots (...
)
inside the brackets. For example:
app/api/post/[...slug].js
matches /api/post/a
, but also
/api/post/a/b
, /api/post/a/b/c
and so on.Note: You can use names other than
slug
, such as:[...param]
Matched parameters will be passed as a query parameter (slug
in the
example) to the api handler, and it will always be an array, so, the path
/api/post/a
will have the following query
object:
{ "slug": ["a"] }
And in the case of /api/post/a/b
, and any other matching path, new
parameters will be added to the array, like so:
{ "slug": ["a", "b"] }
An API route for app/api/post/[...slug].js
could look like this:
const handler: BlitzApiHandler = (req, res) => {
const {
query: { slug },
} = req
res.end(`Post: ${slug.join(", ")}`)
}
export default handler
Now, a request to /api/post/a/b/c
will respond with the text:
Post: a, b, c
.
Catch all routes can be made optional by including the parameter in double
brackets ([[...slug]]
).
For example, app/api/post/[[...slug]].js
will match /api/post
,
/api/post/a
, /api/post/a/b
, and so on.
The query
objects are as follows:
{ } // GET `/api/post` (empty object)
{ "slug": ["a"] } // `GET /api/post/a` (single-element array)
{ "slug": ["a", "b"] } // `GET /api/post/a/b` (multi-element array)
app/api/post/create.js
- Will match /api/post/create
app/api/post/[pid].js
- Will match /api/post/1
, /api/post/abc
,
etc. But not /api/post/create
app/api/post/[...slug].js
- Will match /api/post/1/2
,
/api/post/a/b/c
, etc. But not /api/post/create
, /api/post/abc
The response (res
) includes a set of Express.js-like methods to improve
the developer experience and increase the speed of creating new API
endpoints, take a look at the following example:
const handler: BlitzApiHandler = (req, res) => {
res.status(200).json({ name: "Blitz.js" })
}
export default handler
The included helpers are:
res.status(code)
- A function to set the status code. code
must be a
valid
HTTP status coderes.json(body)
- Sends a JSON response. body
must be a valid
serialiazable objectres.send(body)
- Sends the HTTP response. body
can be a string
, an
object
or a Buffer
API routes provide built in middlewares which parse the incoming request
(req
). Those middlewares are:
req.cookies
- An object containing the cookies sent by the request.
Defaults to {}
req.query
- An object containing the
query string. Defaults to
{}
req.body
- An object containing the body parsed by content-type
, or
null
if no body was sentEvery API route can export a config
object to change the default
configs, which are the following:
export const config = {
api: {
bodyParser: {
sizeLimit: "1mb",
},
},
}
The api
object includes all configs available for API routes.
bodyParser
enables body parsing, you can disable it if you want to
consume it as a Stream
:
export const config = {
api: {
bodyParser: false,
},
}
bodyParser.sizeLimit
is the maximum size allowed for the parsed body, in
any format supported by bytes,
like so:
export const config = {
api: {
bodyParser: {
sizeLimit: "500kb",
},
},
}
externalResolver
is an explicit flag that tells the server that this
route is being handled by an external resolver like express or
connect. Enabling this option disables warnings for unresolved requests.
export const config = {
api: {
externalResolver: true,
},
}
You can also use Connect compatible middleware.
For example, configuring CORS for your API endpoint can be done leveraging the cors package.
First, install cors
:
npm i cors
# or
yarn add cors
Now, let's add cors
to the API route:
import Cors from "cors"
// Initializing the cors middleware
const cors = Cors({
methods: ["GET", "HEAD"],
})
// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
function runMiddleware(req, res, fn) {
return new Promise((resolve, reject) => {
fn(req, res, (result) => {
if (result instanceof Error) {
return reject(result)
}
return resolve(result)
})
})
}
async function handler(req: BlitzApiRequest, res: BlitzApiResponse) {
// Run the middleware
await runMiddleware(req, res, cors)
// Rest of the API logic
res.json({ message: "Hello Everyone!" })
}
export default handler