"Api" Routes Introduction
One of the more interesting features of Next.js is the ability specify routes for a Web API to be executed on the same server serving your site. This extends the functionality of the server, in that it not only pre-renders and serves your files but also can act as a back end API for your application!
Recall, when we first created a boilerplate Next.js app, we were we given an "index.js" file within the "pages" folder that rendered the exported "Home" component at the default route ("/"). We were also provided with an "api" folder, containing a single "hello.js" file. Therefore, if we follow the structure of the "pages" folder, we should be able to access a "hello" route from the "api" folder, ie: "http://localhost:3000/api/hello". This is indeed the case, and the server will respond with:
{ "name": "John Doe" }
Route Definitions
If you open the "hello.js" file, you will see some code that looks very similar to how routes are defined in "Express", ie:
File: "pages/api/hello.js"
export default function handler(req, res) {
res.status(200).json({ name: 'John Doe' });
}
'req' and 'res'
You will see that both "req" and "res" objects are available to the exported callback function in order to give us access to the HTTP request / response. However, it is important to note that these are not the same as the "Request" and "Response" objects provided by "Express", though they serve the same purpose.
Additionally, "middleware" functions have been built in to parse the incoming request, which gives the "req" object the following additional properties:
req.cookies - An object containing the cookies sent by the request. Defaults to {}
req.query - An object containing the query string. Defaults to {}" - Note: route parameter values also included.
req.body - An object containing the body parsed by content-type, or null if no body was sent
Similarly, some "helper functions" have been made available on the "res" object to provide additional functionality. These are similar to what is offered by "Express":
res.status(code) - A function to set the status code. code must be a valid HTTP status code
res.json(body) - Sends a JSON response. body must be a serializable object
res.send(body) - Sends the HTTP response. body can be a string, an object or a Buffer
res.redirect([status,] path) - Redirects to a specified path or URL. status must be a valid HTTP status code. If not specified, status defaults to "307" "Temporary redirect".
res.revalidate(urlPath) - Revalidate a page on demand using getStaticProps. urlPath must be a string.
HTTP Methods
At the moment, the "hello" API route responds to "GET" requests only. If we wish to extend this to match other HTTP methods (ie: "POST"), we can leverage the "method" property of the "req" object:
export default function handler(req, res) {
const { method } = req;
switch (method) {
case 'GET':
res.status(200).json({ name: 'John Doe' });
break;
case 'POST':
// return the 'name' value provided in the body of the rquest
res.status(200).json({ name: req.body.name });
break;
default:
// send an error message back, indicating that the method is not supported by this route
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
Dynamic Routes
As with regular routing, API routes may also contain "route parameters". These must be defined in a similar way, in that they must exist in their own .js file with the desired route parameter as the file name. For example, if we wish to match the route "/api/users/id" (where id is the unknown parameter), we would crate the following file:
File: "/pages/api/users/[id].js"
export default function handler(req, res) {
const { id } = req.query; // "id" route parameter
res.status(200).json({ name: `user ${id}` });
}
If we wish to reference the route parameter in the route definition, it can be accessed using req.query.
Web API Structure
Using the above techniques, it is possible to create routes that match that of a typical Web API. For example, consider the following files:
File: "/pages/api/users/index.js"
export default function handler(req, res) {
const { name } = req.body;
const { method } = req;
switch (method) {
case 'GET':
// Read data from your database
res.status(200).json({ message: `TODO: Get All Users` });
break;
case 'POST':
// Create data in your database
res.status(200).json({ message: `TODO: Create User with Name: ${name}` });
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}
File: "/pages/api/users/[id].js"
export default function handler(req, res) {
const { id } = req.query;
const { name } = req.body;
const { method } = req;
switch (method) {
case 'GET':
// Read data from your database
res.status(200).json({ message: `TODO: Get User with id: ${id} ` });
break;
case 'PUT':
// Update data in your database
res.status(200).json({ message: `TODO: Update User with id: ${id} - Set Name: ${name}` });
break;
case 'DELETE':
// Delete data in your database
res.status(200).json({ message: `TODO: Delete User with id: ${id}` });
break;
default:
res.setHeader('Allow', ['GET', 'PUT', 'DELETE']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}