How to handle errors in Next.js api? - javascript

What is the best practice how to organize code in Next.js api handlers? I saw this video and he puts all REST routes in two files:
pages/api/users/index.ts handles all operations that don't require id so GET /api/users - get all users and POST pages/api/users - create a new user
pages/api/users/[id].ts handles all operations that require id so GET api/users/1 - get user by id, PUT /api/users/1 - update user, and DELETE /api/users/1 - delete a user
This means a lot of code will go into just 2 files and handled by a switch case statement. How should all this code be organized?
Every case statement should have its own try catch block for handling database calls which is a lot of repetition, I could make single try catch around entire switch but that will wrap a lot of unnecessary code, and maybe each case needs different handling? I could put single try catch in higher order function and wrap each case block with it but I'm not sure that's nice either?
Also later I will need to protect some routes with withProtected and withRole middlewares but that wont be easy because now multiple api's are handled inside single handler call.
What is the best way to organize this? Is there already good complete example existing?
// pages/api/users/index.ts
import { NextApiRequest, NextApiResponse } from 'next';
import { hash } from 'bcryptjs';
import prisma from 'lib/prisma';
/**
* POST /api/users
* Required fields in body: name, username, email, password
*/
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
): Promise<void> {
const { method, body } = req;
switch (method) {
case 'GET':
// get all
// try catch again?
const users = await prisma.user.findMany();
res.status(200).json({ users });
break;
case 'POST':
// create
try {
const { name, username, email, password: _password } = body;
// todo: validate...
const _user = await prisma.user.findFirst({
where: { email },
});
if (_user)
throw new Error(`The user with email: ${email} already exists.`);
const password = await hash(_password, 10);
const user = await prisma.user.create({
data: {
name,
username,
email,
password,
},
});
res.status(201).json({ user });
} catch (error) {
res.status(500).json({ error });
}
break;
default:
res.setHeader('Allow', ['GET', 'POST']);
res.status(405).end(`Method ${method} Not Allowed`);
}
}

This is how I've done things. It also covers method not allowed
Working code example from my project
src/somewhere/globalAPICall.js
/**
*
* #param {http.IncomingMessage} req
* #param {http.ServerResponse} res
* #param {{[key: string]: () => Promise<void>}} actions
*/
export default async function globalAPICall(req, res, actions) {
try {
const method = req.method
// check an action exists with request.method else throw method not allowed
if (!Object.keys(actions).includes(method)) {
console.log('method not allowed')
throw new MyError('Method not allowed', 405)
}
// run the action matching the request.method
await actions[method]()
} catch(err) {
if (err instanceof MyError) {
res.status(err.code).send(err.message);
} else {
res.status(500).send("Internal server error");
}
}
}
src/pages/api/users.js
/**
*
* #param {http.IncomingMessage} req
* #param {http.ServerResponse} res
*/
export default async function handler(req, res) {
async function POST() {
const { email, password, username } = req.body;
if (!username) {
throw new MyError('Username required', 400)
}
await CreateUser(email, password, username)
res.status(201).send();
}
async function GET() {
const result = await ListUsers()
res.status(200).json(result);
}
await globalAPICall.js(req, res, {POST, GET})
}

Related

jwt.verify keeps on returning invalid signature despite working elsewhere

I have no idea why, but for some reason the jwt.verify function is complaining about an invalid signature in only part of my application. To give you some background, I am trying to set up auth headers using the context function with Apollo Server (which I guess is irrelevant anyway as the jwt.verify function should work in any javascript code snippet the same way) as follows:
context: ({ req }) => {
const token = req.get('Authorization') || '';
if (!token) {
console.log('no token detected. returning null...');
return { user: null };
}
return {
user: verifyUser(token.split(' ')[1]),
};
},
The verifyUser function:
const verifyUser = (token: string) => {
try {
return jwt.verify(token, process.env.JWT_SECRET as Secret);
} catch (error: any) {
console.error('error: ', error.message);
return null;
}
};
Note that I’m following this guide and have renamed getUser to verifyUser.
In each graphql request, I am providing a Bearer token, like so:
{
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwczovL2F3ZXNvbWVhcGkuY29tL2dyYXBocWwiOnsicm9sZXMiOlsiYWRtaW4iXSwicGVybWlzc2lvbnMiOlsicmVhZDphbnlfYWNjb3VudCIsInJlYWQ6b3duX2FjY291bnQiXX0sImlhdCI6MTU4NjkwMDI1MSwiZXhwIjoxNTg2OTg2NjUxLCJzdWIiOiIxMjM0NSJ9.31EOrcKYTsg4ro8511bV5nVEyztOBF_4Hqe0_P5lPps"
}
But every time I make a graphql request (a query or mutation) in the graphql playground, I am getting an invalid token message in the catch block, so presumably the jwt.verify function is failing. I wondered whether the JWT I provided to the Bearer code above is wrong, but I do not think it is. I am getting it from an authenticateUser resolver:
export const authenticateUser = async (
_: undefined,
{ input: { email, password } }: any
): Promise<any> => {
try {
const user = await User.findByEmail(email);
if (!user) {
throw new HttpError(404, 'User not found. Please create an account.');
}
const correctPassword = await bcrypt.compare(password, user.hashedPassword);
if (!correctPassword) {
throw new HttpError(403, 'Wrong password for this account.');
}
// assign object methods to the user instance as objects retrieved from db don't have methods
Object.setPrototypeOf(user, User.prototype);
const token = user.signToken(); // see below
return { token, id: user._id };
} catch (error: any) {
throw new HttpError(error.statusCode, error.message);
}
}
The user.signToken() function comes from a User class:
signToken(expiry: number | string = 60 * 24): string {
const secret = process.env.JWT_SECRET + this.hashedPassword;
// { ...this } overcomes error `Expected "payload" to be a plain object`
const token = jwt.sign({ ...this }, secret, {
expiresIn: expiry,
});
return token;
}
The only difference is that I am passing in a hashed password with the secret argument. I’ve also noticed that the error does not occur when using jwt.sign instead of jwt.verify (like in this post), but I assume I have to use the verify function.
Does anyone know why my code may not be working? The guide I am following does not pass a hashed password into the secret argument.

Angularfire check password

I'm coding a "delete account" functionality on my app and I want the user to enter their password again before triggering the deletion.
What would be the best way to implement this? I was thinking on using the "signInWithEmailAndPassword" method and capturing the result to check if the credentials are correct, but I'm afraid that would overwrite the current session.
Any tips?
If the session is too old or whatever, an error is thrown by the 'delete account' function anyways. Then you need to re-auth the user. There is a special function for that too: reauthenticateWithCredential().
Here I have an example to show the difference of the login and re-authenticate functions (copied from a project of mine and cut down a bit because there was some analytics and stuff):
public async reAuthenticate(user: User, { email, password }: IEmailLoginData): Promise<UserCredential> {
const credentials = firebase.auth.EmailAuthProvider.credential(email, password);
return user.reauthenticateWithCredential(credentials)
.catch(e => {
console.error(e);
throw e;
});
}
public async login({ email, password }: IEmailLoginData): Promise<UserCredential> {
return firebase.auth().signInWithEmailAndPassword(email, password)
.catch(e => {
console.error(e);
throw e;
});
}
// PS: IEmailLoginData is a custom interface from me, but it just contains email and password
Also, here is the code for the 'delete account'. It should be pretty self-explanatory - hope it helps a bit:
async delete(): Promise<void> {
const dialogRef = this.dialog.open(YesNoDialogComponent, {
data: {
yes: 'Yes',
no: 'No',
title: 'Are you sure that you want to delete your account?'
}
});
const result = await dialogRef.afterClosed().pipe(take(1)).toPromise();
if (result === IYesNoDialogResult.YES) {
try {
const authUser = await this.auth.nextAuthUser(); // Getting the current firebase user from my custom service
await authUser.delete();
await this.router.navigateByUrl('login');
} catch(e) {
const toast = await this.toast.create({
duration: 3000,
message: 'This is a sensitive operation. Please login again to do this'
});
await toast.present();
await this.router.navigateByUrl('reauth');
});
}
}
For different auth provider it might be slightly different, but in the essence it is still the same. Just for example with google (if you want to use the Ionic Native Google Plus Login Plugin), you need to create the re-authenticate credentials from the plugin result:
public async reAuthenticate(user: User): Promise<UserCredential> {
try {
if (this.platform.is('cordova')) {
try {
const gUser = await this.gPlus.login({
webClientId: environment.googleWebClientId,
offline: true,
scopes: 'profile email'
});
const credential = firebase.auth.GoogleAuthProvider.credential(gUser.idToken);
return await user.reauthenticateWithCredential(credential);
} catch (nativeE) { // If login failed via native method, fallback to redirects
if (nativeE == 12501 || nativeE == 13) { // User cancelled login
return null;
}
console.error(nativeE);
// In constructor:
// this._provider = new firebase.auth.GoogleAuthProvider();
await user.reauthenticateWithRedirect(this._provider);
return await firebase.auth().getRedirectResult();
}
}
else {
return await user.reauthenticateWithPopup(this._provider);
}
} catch (e) {
console.error(e);
throw e;
}
}

Is my code the best way to use async await?

Am try to implement and learn async await functions in my login example, but I don't know if is the best, elegant and clean code. I have doubs meanly in catch errors, and if I need implement in a best way the const and functional programing. Can share your opinions?
app.post('/', async (req, res) => {
try {
const { email } = req.body.email; // destructuring
const usuarioEncontrado = await Usuario.findOne({email: email});
// Validate user exist
if (!usuarioEncontrado) { // when not exist return null
throw res.status(404).json({error: 'El usuario no existe'});
}
// validate user pass
if (!bcrypt.compareSync(req.body.password, usuarioEncontrado.password)) {
throw res.status(404).json({error: 'No match'});
}
const token = jwt.sign( // generate token
{
usuario: usuarioEncontrado
},
SEED,
{
expiresIn: (60 * 60)
}
);
res.status(200).json({ // send response
token: token,
usuario: usuarioEncontrado
});
} catch (e) { // send error
res.status(404).json(e);
}
}
THANKS
Your code shows a couple problems:
You're attempting to send double responses. First you do throw res.status(404).json(...). Then, you catch that exception and do res.status(404).json(e) again. That's not right. If you're going to send the response, then just return, don't throw. Or, just throw the exception without sending a response and send the actual error response from the catch handler.
Also, throw res.status(404).json({error: 'No match'}); sends the response and then throws whatever .json() returns which is probably not what you want. That won't be an error object of any kind.
I prefer to centralize the places I send an error response to one place in the request handler. That keeps you from ever attempting to send multiple responses and just makes the flow of the request handler easier to understand (in my opinion).
To do that, I just throw a custom error that may have a custom message/status associated with it and then catch all possible errors in one place. Here's one way to do that. The myError class can be used everywhere in your project, not specific to just one route. The idea is that often when you throw, you know in that context what you want the status and message to be so you set that in the custom Error object and can then use that info in the catch. The catch then has to determine whether it has your custom error or just a regular error. First, I have a reusable Error subclass that lets me throw, not only a message, but also a status value.
// reusable error class that contains a status in addition to the message
class MyError extends Error {
// this static method saves having to compare if it's a custom error object or not
// every time we use this
static sendError(res, e, status = 500) {
if (e instanceof MyError) {
e.sendError(res);
} else {
res.sendStatus(status);
}
}
constructor(msg, status = 500) {
// allow calling with or without new
if (!(this instanceof MyError)) {
return new MyError(msg, status);
}
super(msg);
this.status = status;
}
sendError(res) {
res.status(this.status).send(this.message);
}
}
And, then here's how you use that in your code and centralize the sending of the error status.
app.post('/', async (req, res) => {
try {
const { email } = req.body.email; // destructuring
const usuarioEncontrado = await Usuario.findOne({email: email});
// Validate user exist
if (!usuarioEncontrado) { // when not exist return null
throw MyError('El usuario no existe', 404);
}
// validate user pass
if (!bcrypt.compareSync(req.body.password, usuarioEncontrado.password)) {
throw MyError('No Match', 404);
}
const token = jwt.sign( // generate token
{
usuario: usuarioEncontrado
},
SEED,
{
expiresIn: (60 * 60)
}
);
res.status(200).json({ // send response
token: token,
usuario: usuarioEncontrado
});
} catch (e) { // log and send error response
// e may be either MyError or some other system generated Error
console.log(e);
MyError.sendError(res, e);
}
}

Request body values not being picked up in firebase cloud function [duplicate]

When sending {"identifiant": "iJXB5E0PsoKq2XrU26q6"} to the below Cloud Function, I cannot get the identifiant value in the request body and it will always return PROBLEMAS NO REQUEST.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
admin.initializeApp();
exports.meusCandidatos = functions.https.onRequest((req, res) => {
const identifiant = req.body.identifiant;
if(identifiant) res.status(200).json('ok').end();
res.status(500).json('PROBLEMAS NO REQUEST').end();
});
Unlike a Callable function, the body of a request is not parsed automatically and needs to be parsed before you can use it.
In addition, json(...) will call end() internally so you don't need both. Also make sure that you don't call end(), send(), json(), etc. multiple times, as this will lead to errors.
const jsonParser = require('body-parser').json();
exports.meusCandidatos = functions.https.onRequest((req, res) => {
jsonParser(req, res, (err) => {
if (err) {
res.status(500).json({error: err.message});
return; // stop here
}
const identifiant = req.body.identifiant;
if (!identifiant) {
res.status(500).json({error: 'PROBLEMAS NO REQUEST'});
return; // stop here
}
// continue
res.status(200).json({ status: 'ok' });
})
});

Can't use #Res() with FilesInterceptor()

I am trying to upload a file using builtin multer and after then sending the response back to the user for success or failure. It was all going good until today, when I try to upload the Response wont come. after digging a bit I find out that when i use #res with #UploadedFile it does not execute the controller. I am new to nest.js.
Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
} catch (error) {
console.log(error);
return error;
}
}
Not Working.
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar, #Res() res) {
console.log(req.body);
if (!req.body.user_id) {
throw new Error('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
); // returns the url for the uploaded image
return resultUpload;
res.send(resultUpload);
} catch (error) {
console.log(error);
res.send(error);
}
}
In nest, you should always avoid injecting #Res because then you lose a lot of things that make nest so great: interceptors, exception filters,...
And actually, in most cases you don't need #Res since nest will automatically handle sending the response correctly.
If you want to send data from a controller method, you can just return the data (Promises and Observables will be resolved automatically as well). If you want to send an error to the client, you can just throw the corresponding HttpException, e.g. 404 -> NotFoundException:
#Post('uploads/avatar')
async uploadFile(#Req() req, #UploadedFile() avatar) {
if (!req.body.user_id) {
// throw a 400
throw new BadRequestException('id params not found.');
}
try {
const resultUpload = await this.userService.uploadUserImage(
req.body.user_id,
avatar,
);
return resultUpload;
} catch (error) {
if (error.code === 'image_already_exists') {
// throw a 409
throw new ConflictException('image has already been uploaded');
} else {
// throw a 500
throw new InternalServerException();
}
}
}
If for some reason you have to inject #Res here, you cannot use the FilesInterceptor. Then you have to configure the multer middleware yourself.
Side note
You can create a custom decorator for accessing the userId:
import { createParamDecorator } from '#nestjs/common';
export const UserId = createParamDecorator((data, req) => {
if (!req.body || !req.body.user_id) {
throw new BadRequestException('No user id given.')
}
return req.body.user_id;
});
and then use it in your controller method like this:
#Post('uploads/avatar')
async uploadFile(#UserId() userId, #UploadedFile() avatar) {
look, when you are using an interceptor, you are handling (with using .handle()) the stream of response (observable) not a whole package of it, but using express #Res actually is somehow getting around the whole flow of response streaming.
this is also explicitly mentioned in nestjs official documents:
We already know that handle() returns an Observable. The stream
contains the value returned from the route handler, and thus we can
easily mutate it using RxJS's map() operator.
WARNING
The response mapping feature doesn't work with the
library-specific response strategy (using the #Res() object directly
is forbidden).

Categories

Resources