API declarativa en Next.JS: ¿una realidad?

¡Oye! Mi nombre es Andrey, soy desarrollador Backend Node.JS en una de las empresas extranjeras que desarrollan sistemas para la administración de oficinas. Nuestra aplicación y su versión web brindan a los propietarios la capacidad de rastrear la ocupación de la oficina, conectar dispositivos de IoT para rastrear, por ejemplo, la cantidad de comida en los refrigeradores o la cantidad de agua que queda en los refrigeradores, emitir pases para los empleados en su edificio y mucho más. más. Uno de los nodos más importantes de este sistema es la API tanto para los usuarios internos que utilizan una aplicación o sitio web como para los clientes que utilizan nuestra solución Whitelabel. En total, más de doscientos puntos finales API están registrados en nuestro sistema, para cuya construcción utilizamos el marco NestJS. Si por alguna razón aún no ha oído hablar de Nest,entonces te recomiendo encarecidamente que leas el artículoNestJS es el verdadero backend de nodejs . Una de las características principales y más importantes de NestJS es el soporte nativo para decoradores, que a su vez le permite crear puntos finales de forma declarativa.





@Get('/v2/:key')
@HttpCode(HttpStatus.OK)
async getContentFromCacheByKey(
	@Param('key') key: string,
): Promise<GenericRedisResponse> {
	const result = await this.cacheService.get(key);

	if (!result) {
		throw new NotFoundException(`There is no key ${key} in cache`);
	}

	return result;
}
      
      



Los decoradores son especialmente útiles cuando es necesario aceptar diferentes tipos de solicitudes a lo largo de la misma ruta. Por ejemplo, cuando necesitamos no solo "tomar" datos por clave de la caché, sino también guardar los datos bajo la clave que necesitamos. La ruta sigue siendo la misma, solo cambian el decorador y el contenido del método.





@Post('/v2/:key')
@HttpCode(HttpStatus.NO_CONTENT)
async getContentFromCacheByKey(
	@Param('key') key: string,
  @Body() body: GenericRedisBody,
): Promise<void> {
    await this.cacheService.set(key, body.data, body.ex, body.validFor);
}
      
      



Esto es muy conveniente, aunque solo sea porque no hay necesidad de crear métodos ornamentados con declaraciones condicionales complicadas. Sin mencionar la conveniencia de las pruebas unitarias.





, NestJS, NextJS pet-. , NextJS API, , - NextJS , .





@storyofams/next-api-decorators

API routes NextJS. TypeScript, 100%, . API NextJS. :





// pages/api/user.ts
class User {
  // GET /api/user
  @Get()
  async fetchUser(@Query('id') id: string) {
    const user = await DB.findUserById(id);

    if (!user) {
      throw new NotFoundException('User not found.');
    }

    return user;
  }

  // POST /api/user
  @Post()
  @HttpCode(201)
  async createUser(@Body(ValidationPipe) body: CreateUserDto) {
    return await DB.createUser(body.email);
  }
}

export default createHandler(User);
      
      



, , :





export default async (req: NextApiRequest, res: NextApiResponse) => {
  if (req.method === 'GET') {
    const user = await DB.findUserById(req.query.id);
    if (!user) {
      return res.status(404).json({
        statusCode: 404,
        message: 'User not found'
      })
    }

    return res.json(user);
  } else if (req.method === 'POST') {
    // Very primitive e-mail address validation.
    if (!req.body.email || (req.body.email && !req.body.email.includes('@'))) {
      return res.status(400).json({
        statusCode: 400,
        message: 'Invalid e-mail address.'
      })
    }

    const user = await DB.createUser(req.body.email);
    return res.status(201).json(user);
  }

  res.status(404).json({
    statusCode: 404,
    message: 'Not Found'
  });
}
      
      



. , :





@SetHeader('Content-Type', 'text/plain')
class UserHandler {
  @Get()
  users(@Header('Referer') referer: string) {
    return `Your referer is ${referer}`;
  }
  
  @Get('/json')
  @SetHeader('Content-Type', 'application/json')
  users(@Header('Referer') referer: string) {
    return { referer };
  }
}
      
      



, ! NestJS. class-validator class members :





import { IsNotEmpty, IsEmail } from 'class-validator';

export class CreateUserDTO {
  @IsEmail()
  email: string;

  @IsNotEmpty()
  fullName: string;
}
      
      



, 422 Unprocessable Entity



. , query :





@Get('/users')
@Query('isActive', ParseBooleanPipe({ nullable: true })) isActive?: boolean
      
      



isActive, URL , boolean. :





@Get('/users/:userId')
@Param('userId', ParseNumberPipe) userId: string,
      
      



middleware, , . NestJS , Guards (TLDR: ). , guard, JWT- , :





const JwtAuthGuard = createMiddlewareDecorator(
  (req: NextApiRequest, res: NextApiResponse, next: NextFunction) => {
    if (!validateJwt(req)) {
      throw new UnauthorizedException();
      // 
      return next(new UnauthorizedException());
    }

    next();
  }
);

class SecureHandler {
  @Get()
  @JwtAuthGuard() //     
  public securedData(): string {
    return 'Secret data';
  }
}
      
      



middleware useMiddleware:





@UseMiddleware(() => ...)
class User {
// ...
      
      



, UnauthorizedException



. , , . :













BadRequestException







400







'Bad Request'







UnauthorizedException







401







'Unauthorized'







NotFoundException







404







'Not Found'







PayloadTooLargeException







413







'Payload Too Large'







UnprocessableEntityException







422







'Unprocessable Entity'







InternalServerErrorException







500







'Internal Server Error'







Catch. (, API- 503 ):





import { Catch } from '@storyofams/next-api-decorators';

function exceptionHandler(
  error: unknown,
  req: NextApiRequest,
  res: NextApiResponse
) {
  const message = error instanceof Error ? error.message : 'An unknown error occurred.';
  res.status(200).json({ success: false, error: message });
}

@Catch(exceptionHandler)
class Events {
  @Get()
  public events() {
    return 'Our events';
  }
}
      
      



Este conjunto completo de herramientas le permite describir los puntos finales de la manera más declarativa posible y dividir el código en componentes más pequeños, lo que a su vez simplifica enormemente las pruebas unitarias y la depuración, que es fundamental para preservar la salud mental y el tiempo del desarrollador :)





Puede familiarizarse con la documentación aquí , puede ver el código fuente aquí . ¡Gracias por leer hasta el final y no perder el interés por el camino!








All Articles