Advanced authentication at the Edge using Supabase & Next.JS
Applying httpOnly cookies to a Next.JS app using Supabase at the Edge
Sanna Jammeh
5/25/2022Next.js is a wonderful framework for building web applications. It contains a lot of great features, but the one department thats missing is a more powerful way to build resful APIs.
Next.js is a file system routed framework. As such, any file in the pages/api
directory will be served as an API route. This is a simple example of how it usually looks.
// pages/api/cat.ts
import { NextApiRequest, NextApiResponse } from 'next';
const handler = async (req: NextApiRequest, res: NextApiRequest) => {
const cat = await getCat();
res.status(200).json({
cat: cat;
});
};
export default handler;
When defining GET, POST, PUT, and DELETE methods in this manner, the code becomes quite imperative and difficult to comprehend. Let's make our cat handler into a CRUD endpoint.
// pages/api/cats.ts
import { NextApiRequest, NextApiResponse } from "next";
interface Cat {
name: string;
age: number;
}
const catsHandler = async (req: NextApiRequest, res: NextApiResponse<Cat>) => {
switch (req.method) {
case "GET":
const cats = await getCats();
res.status(200).json({
cats,
});
break;
case "POST":
const cat = req.body.cat as Cat;
const cats = await createCat(cat);
res.status(201).json({
cats,
});
break;
case "PUT":
const cat = req.body.cat as Cat;
const cat = await updateCat(cat);
res.status(200).json({
cat,
});
break;
case "DELETE":
const cat = await deleteCat(req.body.catId as string);
res.status(200).json({
cat,
});
break;
default:
res.status(400).json({
error: "Method not allowed",
});
}
};
export default catsHandler;
This can become incredibly hard to read as the business logic becomes larger and more complex. Additionally, there are no helpers to handle middlewares or validation. Coming from a Nest.js background, I've found it's class based approach to be a bit more intuitive and easier to work with.
This library is made by the wonderful folks over at Story of AMS. Next API Decorators provides a way to define API routes using decorators and provides a very similar api to Nest.js.
Install the library by running the following command:
npm i @storyofams/next-api-decorators
This library requires TypeScript to work and these flags to be enabled in your tsconfig.json
.
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
}
Now lets rewrite the cats handler above using API decorators
import {
createHandler,
Get,
Post,
Put,
Delete,
} from "@storyofams/next-api-decorators";
interface Cat {
name: string;
age: number;
}
// pages/api/cats.ts
class CatsHandler {
@Get()
async getCats() {
const cats = await getCats();
return {
cats: cats,
};
}
@Post()
async createCat(@Body() body: Cat) {
const cats = await createCats();
return {
cats: cats,
};
}
@Put()
async updateCat(@Body() body: { id: number } & Cat) {
const cat = await updateCat(body);
return {
cat: cat,
};
}
@Delete()
async deleteCat(@Body() body: { id: number }) {
const cat = await deleteCat(body);
return {
cat: cat,
};
}
}
export default createHandler(Handler);
This is much more readable and easy to understand. The decorators are used to define the route and the body of the request. However, this library is much more powerful than that.
Next api decorators can make use of Next.js's catch all
routes to handle all requests for a directory. We can rewrite our cats handler to gain paths like /api/cats/:id
and /api/cats/:id/:action
. For this we will add a catch all
route to our pages/api
directory and install two additional libraries to handle validation.
npm i class-validator class-transformer
These libraries enables validation of the request body using class decorators and class transformers.
// pages/api/cats/[[...params]].tsx (catch all route)
import {
createHandler,
Get,
Post,
Put,
Delete,
Param,
ValidationPipe,
} from "@storyofams/next-api-decorators";
import { IsString, IsNumber, IsOptional } from "class-validator";
interface Cat {
id: string;
name: string;
age: number;
}
class CatDTO {
@IsString()
name: string;
@IsNumber()
age: number;
}
class UpdateCatDTO {
@IsString()
@IsOptional() // Allowed to be undefined
name?: string;
@IsNumber()
@IsOptional()
age?: number;
}
class CatsHandler {
@Get()
async getCats() {
const cats = await getCats();
return {
cats: cats,
};
}
@Get("/:id")
async getCat(@Param("id") id: number) {
const cat = await getCat(id);
return {
cat: cat,
};
}
@Post()
async createCat(@Body(ValidationPipe) body: CatDTO) {
const cats = await createCats();
return {
cats: cats,
};
}
@Put("/:id")
async updateCat(
@Param("id") id: number,
@Body(ValidationPipe) body: UpdateCatDTO
) {
const cat = await updateCat(body);
return {
cat: cat,
};
}
@Delete("/:id")
async deleteCat(@Param("id") id: number) {
const cat = await deleteCat(body);
return {
cat: cat,
};
}
}
export default createHandler(CatsHandler);
The body of the request is now automatically validated and transformed. To retrieve the id from the url, we utilized the ValidationPipe
decorator and the @Param()
decorator.
Requests to /api/cats/:id
and /api/cats/:id
can now be made to GET
, PUT
, and DELETE
. Nest.js developers, do you feel familiar yet?
Next api decorators also provide all these additional features:
UseMiddleware
decorator to add multiple middlewares to a routeAll these features can be used together to create a very powerful API. Read more about them in their docs.
Applying httpOnly cookies to a Next.JS app using Supabase at the Edge
Generating blurred image placeholders serverside with NextJS to use in the next/image component.