A collection of helpers designed to make fullstack NextJS services easier to create. There are helpers to register API style controllers, a database Repository class designed to work with Prisma, and even testing tools.
The NextJS Documentation says "you can build your entire API with API Routes.", but writing API routes in NextJS sucks. Here is an example handler for a post request:
export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}While this is fine for simple routes, it's easy to see how this doesn't scale.
nextjs-backend-helpers exports a number of use classes and functions to make not suck.
To create a class based controller, simply extend the Controller base class and install it.
Controllers support middleware through their before and after methods.
export class AppController extends Controller {
constructor() {
super()
this.rescue(PrismaClientInitializationError, (error, request, response) => {
Logger.error({
message: 'Looks like we cant reach the database.'
+ 'Is the connection string right?'
+ 'Is the database up?',
})
response.status(500).json({
errors: ['unable to reach database'],
})
})
this.rescue(Error, (error, _request, response) => {
response.status(500).json({
errors: [error.constructor.name, error.message],
})
})
// validate incoming post params against a ZOD type
this.before(this.ensure({
body: z.object({
name: z.string(),
age: z.string().optional()
})
})).only('post')
}
}
type UserIdQuery = {
id: string
}
type UserBody = {
name: string,
birthday: date
}
type GetResponse = {
id: string,
name: string
}
type PostResponse = {
success: true
}
export class UserController extends AppController {
constructor() {
super()
this.before((req: NextApiRequest) => {
console.log(Cookie.get('secret-cookie-value'))
}).only('get')
this.after(() => {
console.log('im running after the post action has run')
}).only('post')
}
async get(request: NextApiRequest, response: NextApiResponse<GetResponse>) {
//...
const {id} = getQuery<UserIdQuery>(request)
const {name, birthday} = getBody<UserBody>(request)
const cookieValue = Cookie.get('secret-cookie-value')
//..
response.json({
id,
name,
cookieValue
})
}
async post(request: NextApiRequest, response: NextApiResponse<PostResponse>) {
// request params have passed validation
response.json({ success: true })
}
}// don't import the default export, but the named export
import { UserController, RequestBuilder } from './user-controller.ts'
import { get, ReponseType, RequestBuilder } from 'nextjs-backend-helpers'
describe('UserController', () => {
describe('returns a user', () => {
let response: ResponseType
let request: RequestBuilder
let user: User
beforeEach(async () => {
request = RequestBuilder().query({
id: '123'
})
response = await get(UserController, request)
})
it("returns the requested user", () => {
expect(response.json).toEqual({
data: {
id: '123',
name: 'Example user'
}
})
})
})
})