Responses can be returned, but they can also be thrown. This enables us to break out of the response pipeline, without creating a pyramid of death or tons of if not, return statements.
#Example
Imagine a /me
route. A get
handler that checks if the user is authenticated,
and returns the user object if that's the case.
Something like:
export const getServerSideProps = handle({ async get({ req }) { if (!req.user) { return redirect('/login'); } return json({ user: req.user }); }, });
That works, but for more complex handlers, the if statements might become tedious. Besides, the logic isn't easily reusable across various api handlers. Response throwing enables us to reuse checks, while flattening the flow.
First, we'll extract the assertion to a reusable helper:
import { redirect } from 'next-runtime'; function assertIsAuthenticated(request) { if (!user.request) { throw redirect('/login', 303); } }
And now we can use that assertion function across our api handlers:
export const getServerSideProps = handle({ async get({ req }) { assertIsAuthenticated(req); return json({ user: req.user }); }, });
Because our assertion throws, instead of returns, we don't need another check.
If the user is not logged in, a redirect response is being sent to the client.
If there is a req.user
object, the assertion will pass and the json result is
returned.
#Typescript
We can decorate the assertion with
typescript assertions
to get the most benefit out of it. Using assertions like below, req.user
is
undefined
before the assertion, while it's User
after
assertIsAuthenticated
.
import { redirect } from 'next-runtime'; function assertIsAuthenticated<T>( request: T, ): asserts request is T & { user: User } { if (!('user' in request)) { throw redirect('/login', 303); } }