Skip to content

Build an API Gateway with NestJs in 10 minutes

This article was written over 18 months ago and may contain information that is out of date. Some content may be relevant but please refer to the relevant official documentation or available resources for the latest information.

Build an API Gateway with NestJs in 10 minutes

This article's intention is to give you a broader perspective into the Microservices architecture. There are many people out there, claiming they have a Microservice oriented architecture, but they lack the core concepts on which this pattern relies. My goal is to write a set of articles looking to clear all the fog that appears when shifting from monolithic, to highly distributed, applications.

The Microservices world is full of interesting and incredibly hard to implement stuff. When you get started, you think that, by just dividing your app in multiple services, you are already there. Sadly, that's almost never true. It's more common than you think to see people building highly critical apps this way, without having in place all the core concepts.

In this article, I'm going to focus on the API Gateway pattern. If you are doing Microservice architecture you SHOULD know it pretty well, being that the case use of this article is to make sure you have clear knowledge on these concepts. If you are enterily new to Microservices, have fun, and enjoy the ride.

In traditional monolithic applications, API clients consume everything from the same location. Although, once you start using microservices, things start to change. You may have multiple services running on entirely different locations.

What API Gateway means

The non deterministic nature of microservice architecture lead us directly to a whole new mess. But what can you do about it? One of the approaches out there is the API Gateway. From a 10,000ft view, it's just an extra service that you put in front of your other services so you can do composition of services.

The Problem

Let's say you have an application that consists of multiple services. We want to have our services' locations hidden from clients, so we'll have a proxy service that has to be able to compose multiple requests.

The Solution

We'll be using NestJs. If you havent used it already, you know that it's pretty similar to Angular, and I think it's a clever way to enable frontend developers to do things on the backend as well. Anyway, it comes out with a CLI tool that allows code generation.

In case you need it

Assuming you know NestJs, or that you've read the articles I've just given you, let's go ahead and start coding. But before we start, you'll need to install the NestJs CLI globally by executing the command npm install -g @nestjs/cli.

Create the first service

In any microservices architecture, you'll find multiple services running, either in the same machine, or in totally distributed places. To start our small proof of concept, we'll create a service using the NestJs CLI. Just follow the next steps:

  1. Create a new folder, and go to it using your preferred command line tool.
  2. Execute nest new service-a. It will prompt you to choose between npm and yarn. I used npm.
  3. Delete the files src/app.controller.spec.ts and src/app.service.ts.
  4. Remove the AppService usages from the AppModule.
  5. Remove the AppService usages from theAppController.

The AppModule will end up looking like this:

// src/app.module.ts
import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";

@Module({
  imports: [],
  controllers: [AppController],
  providers: []
})
export class AppModule {}

The AppController will end up looking like this:

import { Controller, Get } from "@nestjs/common";

@Controller()
export class AppController {
  @Get()
  getHello(): string {
    return "hello";
  }
}

You've got yourself your first service! Now is time to transform it into a microservice. Thankfully, NestJs covers a lot of it for you. By default, NestJs applications are generated as a server that uses HTTP as its transport layer. In the case of microservices, that's not what you want. When working with microservices, you are commonly using TCP instead.

If you are confused about HTTP or TCP, imagine they are just languages. A traditional Http Server talks in English, and a microservice using TCP talks in Spanish.

Since the service is structurally ready to be transformed to a microservice using NestJs, we'll do the next steps first:

  1. Go to the service folder using you preferred command line tool
  2. Execute the command npm i --save @nestjs/microservices
  3. Update the entry point of the service src/main.ts with the service configuration
  4. Update the AppController to use the Microservice Message pattern to serve clients

The entry point should end up looking like this:

import { NestFactory } from "@nestjs/core";
import { Transport } from "@nestjs/microservices";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";

const logger = new Logger();

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.TCP,
    options: {
      host: "127.0.0.1",
      port: 8888
    }
  });
  app.listen(() => logger.log("Microservice A is listening"));
}
bootstrap();

Are you wondering what's going on here? Let me explain it.

  1. We are using the createMicroservice instead of the default create.
  2. Now we have to provide an extra argument for the Transport and Microservice Options.
  3. Inside the microservice options, we tell NestJs the host and port we want to use.

NOTE: You can choose the host, and port of your preference. Also, NestJs has multiple transport options you can choose from.

The AppController will end up looking like this:

import { Controller } from "@nestjs/common";
import { MessagePattern } from "@nestjs/microservices";
import { of } from "rxjs";
import { delay } from "rxjs/operators";

@Controller()
export class AppController {
  @MessagePattern({ cmd: "ping" })
  ping(_: any) {
    return of("pong").pipe(delay(1000));
  }
}

Instead of using the classic Get decorator, we use the MessagePattern. What this will do is trigger the ping method when it receives a ping command. Then, it just returns the string pong after a second delay.

If you want to skip ahead, you can access this working version of create the first service.

Build the API Gateway

You have a new service to run, but how can you access it? That's what we are going to do next. We'll create a new service that works as a HTTP Server, and will map the request to the right service. This will look like a proxy that also allows you to compose requests, and reduce bandwidth usage in your application.

If you are wondering who uses this, AWS offers it as SaaS. Netflix even built their own solution.

Let's use your knowledge of the NestJs CLI:

  1. Go to the directory where service-a project is located using your preferred command line tool.
  2. Execute nest new api-gateway. It will prompt you to choose between npm and yarn. I used npm.
  3. Delete the files src/app.controller.spec.ts.

You are probably thinking, is that it? Well, no. But we are almost there. It's now time to hook the method we created.

  1. Go to the API Gateway root folder using your preferred command line tool.
  2. Execute the command npm i --save @nestjs/microservices.
  3. Import the ClientModule and register the ServiceA.
  4. Inject the new service into the AppService and create a method to query the ServiceA.
  5. Use the new method from the AppService in the AppController.

The AppModule will end up looking like this:

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { ClientsModule, Transport } from "@nestjs/microservices";
import { AppService } from "./app.service";

@Module({
  imports: [
    ClientsModule.register([
      {
        name: "SERVICE_A",
        transport: Transport.TCP,
        options: {
          host: "127.0.0.1",
          port: 8888
        }
      }
    ])
  ],
  controllers: [AppController],
  providers: [AppService]
})
export class AppModule {}

As you can see we need to setup the client to the service using the same transport and options but we give it a new property name to identify the instance of the service. You can also create a custom provider in order to fetch its configuration either from a service that can be local or externally accesed using HTTP.

The AppService will end up looking like this:

import { Injectable, Inject } from "@nestjs/common";
import { ClientProxy } from "@nestjs/microservices";
import { map } from "rxjs/operators";

@Injectable()
export class AppService {
  constructor(
    @Inject("SERVICE_A") private readonly clientServiceA: ClientProxy
  ) {}

  pingServiceA() {
    const startTs = Date.now();
    const pattern = { cmd: "ping" };
    const payload = {};
    return this.clientServiceA
      .send<string>(pattern, payload)
      .pipe(
        map((message: string) => ({ message, duration: Date.now() - startTs }))
      );
  }
}

What we are doing here is injecting the Client we imported in the AppModule using its name as the token to identify it. Then, we create a simple method that gets the current time in milliseconds, sends a message to the service instance, and, once it gets a response, maps it to an object with the response message, and its total duration.

The AppController will end up looking like this:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("/ping-a")
  pingServiceA() {
    return this.appService.pingServiceA();
  }
}

If you start api-gateway, and service-a services, using npm run start:dev, you'll be able to send a GET request to the API gateway by invoking http://localhost:3000/ping-a and get, as a response, an object with a message saying pong and the duration it took.

Although, this is not that impressive right? We could do this with a simple proxy. Things get slightly more complicated when you want to compose requests. But before we can do this, we'll need to create a new service. Go ahead and create the second service, and hook it on the API Gateway as I've just shown you.

If you want to skip ahead, you can access the api gateway with one service or the api gateway with the two services.

NOTE: In the second service, I used a delay of 2 seconds so we can see the difference between services available.

Composing Requests

We have everything in place- two services than can be running anywhere communicating through a single interface bringing more security and modularity to the application. But we want more. What if we had 12 services, and we had to do over 100 requests to fill all the information in a single page? Things will start to get out of hand.

We need a way to compose requests in the API Gateway. For this, I'm going to use some RxJs. The AppController of the API Gateway will end up looking like this:

import { Controller, Get } from "@nestjs/common";
import { AppService } from "./app.service";
import { zip } from "rxjs";
import { map } from "rxjs/operators";

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get("/ping-a")
  pingServiceA() {
    return this.appService.pingServiceA();
  }

  @Get("/ping-b")
  pingServiceB() {
    return this.appService.pingServiceB();
  }

  @Get("/ping-all")
  pingAll() {
    return zip(
      this.appService.pingServiceA(),
      this.appService.pingServiceB()
    ).pipe(
      map(([pongServiceA, pongServiceB]) => ({
        pongServiceA,
        pongServiceB
      }))
    );
  }
}

The only new thing is the pingAll method. If you havent seen RxJs before, this might look like some dark magic, but it's actually quite simple. We want to start the execution of our asynchronous calls at the same time, and consolidate all the responses into a single one.

NOTE: The zip method takes N observables and emits once all have emitted.

If you dont want to do any of this by yourself, just access this working version of the application.

Conclusion

And just like that, you got the API Gateway to compose requests for you. This is just a taste of what Microservices can do for your architecture. There are many more patterns, like API Gateway, that you can explore. A cool homework would be to create a new service that keeps track of the running services, and extending the imports using providers, to allow dynamically setting the client specification.

This Dot Inc. is a consulting company which contains two branches : the media stream, and labs stream. This Dot Media is the portion responsible for keeping developers up to date with advancements in the web platform. This Dot Labs provides teams with web platform expertise, using methods such as mentoring and training.

This Dot is a consultancy dedicated to guiding companies through their modernization and digital transformation journeys. Specializing in replatforming, modernizing, and launching new initiatives, we stand out by taking true ownership of your engineering projects.

We love helping teams with projects that have missed their deadlines or helping keep your strategic digital initiatives on course. Check out our case studies and our clients that trust us with their engineering.

You might also like

Combining Validators and Transformers in NestJS cover image

Combining Validators and Transformers in NestJS

When building a new API, it is imperative to validate that requests towards the API conform to a predefined specification or a contract. For example, the specification may state that an input field must be a valid e-mail string. Or, the specification may state that one field is optional, while another field is mandatory. Although such validation can also be performed on the client side, we should never rely on it alone. There should always be a validation mechanism on the server side as well. After all, you never know who's acting on behalf of the client. Therefore, you can never fully trust the data you receive. Popular backend frameworks usually have a very good support for validation out of the box, and NestJS, which we will cover in this blog post, is no exception. In this blog post, we will be focusing on NestJS's validation using ValidationPipe- specifically on one lesser known feature- which is the ability to not only validate input, but transform it beforehand as well, thereby combining transformation and validation of data in one go. Using ValidationPipe To test this out, let's build a UsersController that supports getting a list of users, and with the option to filter by several conditions. After scaffolding our project using nest new [project-name], let's define a class that will represent this collection of filters, and name it GetUsersQuery: ` Now, let's use it in the controller: ` The problem with this approach is that there is no validation performed whatsoever. Although we've defined userIds as an array of strings, and pageSize as a number, this is just compile-time verification - there is no runtime validation. In fact, if you execute a GET request on http://localhost:3000/users?userIds=1,2,3&pageSize=3, the query object will actually contain only string fields: ` There's a way to fix this in NestJS. First, let's install the dependencies needed for using data transformation and validation in NestJS: ` As their names would suggest, the class-validator package brings support for validating data, while the class-transformer package brings support for transforming data. Each package adds some decorators of their own to aid you in this. For example, the class-validator package has the @IsNumber() decorator to perform runtime validation that a field is a valid number, while the class-transformer package has the @Type() decorator to perform runtime transformation from one type to another. Having that in mind, let's decorate our GetUsersQuery a bit: ` This is not enough, though. To utilize the class-validator decorators, we need to use the ValidationPipe. Additionally, to utilize the class-transformer decorators, we need to use ValidationPipe with its transform: true flag: ` Here's what happens in the background. As said earlier, by default, every path parameter and query parameter comes over the network as a string. We _could_ convert these values to their JavaScript primitives in the controller (array of strings and a number, respectively), or we can use the transform: true property of the ValidationPipe to do this automatically. NestJS does need some guidance on how to do it, though. That's where class-transformer decorators come in. Internally, NestJS will use Class Transformer's plainToClass method to convert the above object to an instance of the GetUsersQuery class, using the Class Transformer decorators to transform the data along the way. After this, our object becomes: ` Now, Class Validator comes in, using its annotations to validate that the data comes in as expected. Why is Class Validator needed if we already transformed the data beforehand? Well, Class Transformer will not throw any errors if it failed to transform the data. This means that, if you provided a string like "testPageSize" to the pageSize query parameter, our query object will actually come in as: ` And this is where Class Validator will kick in and raise an error that pageSize is not a proper number: ` Other transformation options The @Type and @Transform decorators give us all kinds of options for transforming data. For example, strings can be converted to dates and then validated using the following combination of decorators: ` We can do the same for booleans: ` If we wanted to define advanced transformation rules, we can do so through an anonymous function passed to the @Transform decorator. With the following transformation, we can also accept isActive=1 in addition to isActive=true, and it will properly get converted to a boolean value: ` Conclusion This was an overview of the various options you have at your disposal when validating and transforming data. As you can see, NestJS gives you many options to declaratively define your validation and transformation rules, which will be enforced by ValidationPipe. This allows you to focus on your business logic in controllers and services, while being assured that the controller inputs have been properly validated. You'll find the source code for this blog post's project on our GitHub....

Introduction to RESTful APIs with NestJS cover image

Introduction to RESTful APIs with NestJS

Welcome to an introduction to RESTful APIs with NestJS. Understanding JavaScript and TypeScript will make it easier to follow the directions in this article, but you don't necessarily need to be proficient. NestJS is one of the most rapidly growing frameworks for building Node.js server-side applications. Companies such as *Roche*, *Adidas*, and *Autodesk*, all trust NestJS when it comes to building efficient and scalable server-side applications. NestJS is based heavily on *Angular*, and uses Angular-like modules, services, controllers, pipes, and decorators. This allows NestJS to help developers create scalable, testable, loosely coupled, and easily maintainable applications. NestJS was built with TypeScript, but it can also support pure JavaScript development. NestJS offers a level of abstraction above two very popular Node.js frameworks, either *Express.js* or *Fastify*. This means that all of the great middleware that are available for Express.js and Fastify can also be used with NestJS. The best way to get familiar with NestJS is to build a basic RESTful API with CRUD (Create, Read, Update, and Delete) functionality. This is exactly what we'll be doing together in this article. We'll be building a simple RESTful API for a Blog, with endpoints to handle CRUD operations on blog posts. Getting Started Code editor Using Visual Studio Code as a code editor can help speed up NestJS development because of its smart IntelliSense and great TypeScript support. In Visual Studio Code, make sure that you have the following user setting by going to File / Preferences / Settings, and searching for the user setting named typescript.preferences.importModuleSpecifier. Make sure to set it to relative as seen below. ` This will allow Visual Studio Code to use relative paths rather than absolute paths when auto-importing. Using absolute path imports in our application can lead to problems if and when our code ends up in a different directory. Insomnia Insomnia is a useful API testing tool that we will use to test the NestJS API that we will be building. The NestJS CLI To get started with NestJS, let's install the Nest CLI. The Nest CLI is a command-line interface tool that makes it easy to develop, and maintain NestJS applications. It allows us to run our application in development mode, and to build and bundle it for a production-ready release. ` Creating a new NestJS project With the Nest CLI now installed, we can use it to create a new project. ` This command will create a new project directory called rest-api. It will create a base structure for our project, and add in the following core Nest files: - app.controller.ts: A controller with a single route. - app.controller.spec.ts: The controller's unit tests. - app.module.ts: The root module of our application. - app.service.ts: A service for the AppModule's business logic. - main.ts: The entry file of our application. The initial project structure created by the Nest CLI encourages us to follow the common convention of keeping each module in its own directory. Testing the sample endpoint The installation of NestJS comes with a sample API endpoint that we can test by making a request to it. If we open app.controller.ts, we can see that there is a GET endpoint that was created for us with the @Get() decorator. It returns a 'Hello World!' string. ` Let's run npm run start:dev from our project folder. This will run our NestJS app in watch mode, which provides live-reload support when application files are changed. Once NestJS is running, let's open http://localhost:3000 in our web browser. We should see a blank page with the Hello World! greeting. We can also use API testing tools such as Insomnia to make a GET request to http://localhost:3000. We should get the same Hello World! greeting as our result. Let's remove this endpoint since it was only added by the Nest CLI for demo purposes. Go ahead and delete app.controller.ts, app.service.ts, and app.controller.spec.ts. Also, delete all references to AppController and AppService in app.module.ts. Creating a feature module The architectural design of NestJS encourages feature modules. This feature-based design groups the functionality of a single feature in one folder, registered in one module. This design simplifies the codebase and makes code-splitting very easy. Module We create modules in NestJS by decorating a class with the @Module decorator. Modules are needed to register controllers, services, and any other imported sub-modules. Imported sub-modules can have their own controllers, and services, registered. Let's use the Nest CLI to create the module for our blog posts. ` This gives us an empty PostsModule class in the posts.module.ts file. Interface We will use a TypeScript interface to define the structure of our JSON object that will represent a blog post. An interface is a virtual or abstract structure that only exists in TypeScript. Interfaces are used only for type-checking purposes by the TypeScript compiler. TypeScript interfaces do not produce any JavaScript code during the transpilation of TypeScript to JavaScript. Let's use the Nest CLI to create our interface. ` These commands allow us to create a posts.interface.ts file in the feature-based folder for our blog posts, which is /src/posts. The TypeScript interface keyword is used to define our interface. Let's make sure to prefix it with export to allow this interface to be used throughout our application ` From the command-line prompt, let's reset our current working directory back to the root folder of our project by using the following command. ` Service Services are classes that handle business logic. The PostsService that we will be creating will handle the business logic needed to manage our blog posts. Let's use the Nest CLI to create the service for our blog posts. ` This will give us an empty PostsService class in the posts.service.ts file. The @Injectable() decorator marks the PostsService class as a provider that we can register in the providers array of our PostsModule, and then inject into our controller class. More on this later. Controller Controllers are classes that handle incoming requests and return responses to the client. A controller can have more than one route or endpoint. Each route or endpoint can implement its own set of actions. The NestJS routing mechanism handles the routing of requests to the right controller. Let's use the Nest CLI to create the controller for our blog posts. ` This will give us an empty PostsController class in the posts.controller.ts file. Let's inject our PostsService into the constructor of the PostsController class. ` NestJS uses dependency injection to set up a reference to PostsService from within our controller. We can now use the methods provided by PostsService by referencing this.postsService from within our controller class. Registering our controller and service Let's make sure that our PostsModule registers our PostsController and PostsService. The nest generate service post and nest generate controller post commands that we ran earlier have automatically registered the PostsService and PostsController classes in the PostsModule for us. ` NestJS uses the term providers to refer to service classes, middleware, guards, and more. If the PostsService class needs to be made available to other modules within our application, we can export it from PostsModule by using the exports array. This way, any module importing the PostsModule will be able to use the PostsService. ` Importing PostsModule into AppModule We now have one cohesive and well-organized module for all the functionality related to our blog posts. However, all the functionality that will be provided by our PostsModule is not available to our application unless the AppModule imports it. If we revisit the AppModule, we can see that the PostsModule has been automatically added to the imports array by the nest generate module post command that we ran earlier. ` Adding Service and Controller logic Our PostsService and PostsController have no functionality implemented at the moment. Let's now implement our *CRUD* endpoints and their corresponding logic while adhering to RESTful standards. NestJS makes it easy to use MongoDB (using the @nestjs/mongoose package), or PostgreSQL (using *Prisma* or *TypeORM*) to persist and manage our application's data. However, for the sake of simplicity, let's create a local array in our service to mock a database. ` A service, or provider, as NestJS refers to them, can have its scope configured. By default, a service has a *singleton* scope. This means that a single instance of a service is shared across our entire application. The initialization of our service will occur only once during the startup of our application. The default singleton scope of services means that any class that injects the PostsService will share the same posts array data in memory. Get all posts Let's add a method to our PostsService that will return all of our blog posts. ` Let's add a method to our PostsController that will make the logic of the service's findAll() method available to client requests. ` The @Get decorator is used to create a GET /posts endpoint. The /posts path of the request comes from the @Controller('posts') decorator that was used in order to define our controller. Get one post Let's add a method to our PostsService that will return a specific blog post that a client may be looking for. If the id of the requested post is not found in our list of posts, let's return an appropriate 404 NOT FOUND HTTP error. ` Let's add a method to our PostsController that will make the logic of the service's findOne() method available to client requests. ` The @Get decorator is used with a parameter as @Get(':id') here to create a GET /post/[id] endpoint, where [id] is an identification number for a blog post. @Param, from the @nestjs/common package, is a decorator that makes route parameters available to us as properties in our method. @Param values are always of type string. Since we defined id to be of type number in TypeScript, we need to do a string to number conversion. NestJS provides a number of pipes that allow us to transform request parameters. Let's use the NestJS ParseIntPipe to convert the id to a number. Create a post Let's add a method to our PostsService that will create a new blog post, assign the next sequential id to it, and return it. If the title is already being used by an existing blog post, we will throw a 422 UNPROCESSABLE ENTITY HTTP error. ` Let's add a method to our PostsController that will make the logic of the service's create method available to client requests. ` The @Post decorator is used to create a POST /post endpoint. When we use the NestJS decorators for the POST, PUT, and PATCH HTTP verbs, the *HTTP body* is used to transfer data to the API, typically in JSON format. We can use the @Body decorator to parse the *HTTP body*. When this decorator is used, NestJS will run JSON.parse() on the *HTTP body* and provide us with a JSON object for our controller method. Within this decorator, we declare post to be of type Post because this is the data structure that we are expecting the client to provide for this request. Delete a post Let's add a method to our PostsService that will delete a blog post from our in-memory list of posts using the JavaScript splice() function. A 404 NOT FOUND HTTP error will be returned if the id of the requested post is not found in our list of posts. ` Let's add a method to our PostsController that will make the logic of the service's delete method available to client requests. ` Update a post Let's add a method to our PostsService that will find a blog post by id, update it with newly submitted data, and return the updated post. A 404 NOT FOUND HTTP error will be returned if the id of the requested post is not found in our list of posts. A 422 UNPROCESSABLE ENTITY HTTP error will be returned if the title is being used for another blog post. ` Let's add a method to our PostsController that will make the logic of the service's update method available to client requests. ` We use the @Put decorator to make use of the HTTP PUT request method. PUT can be used to both create and update the state of a resource on the server. If we know that a resource already exists, PUT will replace the state of that resource on the server. Testing our feature module Let's start our development server using npm run start:dev. Then, let's open the Insomnia application to test the API endpoints that we created for the PostsModule. Get all posts Let's make a GET request to http://localhost:3000/posts. The result should be a 200 OK success code with an empty array. Create a post Let's make a POST request to http://localhost:3000/posts using the following as our JSON body. ` We should get a successful 201 Created response code, along with a JSON representation of the post that was created, including the id field that has been automatically generated. Get a post Let's make a GET request to http://localhost:3000/posts/1. The result should be a successful 200 OK response code, along with the data for the post with an id of 1. Update a post Let's make a PUT request to http://localhost:3000/posts/1 using the following as our JSON body. ` The result should be a successful 200 OK response code, along with a JSON representation of the post that was updated. Delete a post Let's make a DELETE request to http://localhost:3000/posts/1. The result should be a successful 200 OK response code with no JSON object returned. Logging NestJS makes it easy to add logging to our application. Rather than using console.log() statements, we should use the Logger class provided by NestJS. This will provide us with nicely formatted log messages in the Terminal. Let's add logging to our API. The first step is to define the logger in our service class. ` With the logger now defined, we can add log statements in our service class. Here is an example of a log statement that we can add as the first line of the findAll() method in the PostsService class. ` Such statements provide us with a convenient log message in the Terminal every time a service method is called during a client's request to our API. When a GET /posts request is sent, we should see the following message in the Terminal. ` Swagger NestJS makes it easy to add the *OpenAPI* specification to our API using the NestJS *Swagger* package. The OpenAPI specification is used to describe RESTful APIs for documentation and reference purposes. Swagger setup Let's install Swagger for NestJS. ` Swagger configuration Let's update the main.ts file that bootstraps our NestJS application by adding Swagger configuration to it. ` With our NestJS app running, we can now go to http://localhost:3000/api to view the Swagger documentation for our API. Notice that default is displayed above the routes for our posts. Let's change that by adding @ApiTags('posts') right below @Controller('posts') in our PostsController class. This will replace default with posts to indicate that this set of endpoints belongs to the posts feature model. ` ApiProperty Let's make the properties of our PostModel interface visible to Swagger. We do so by annotating fields with the ApiProperty() decorator, or the ApiPropertyOptional() decorator for optional fields. To use these decorators, we must first change our interface to a class in the posts.interface.ts file. ` Within the ApiProperty() decorator applied to each field, we describe the field type. The id field is optional since we don't know of a new blog post's id when creating it. A date-time string format is used for the date field. These changes allow the PostModel structure to be documented within Swagger. With our NestJS app running, we can now go to http://localhost:3000/api to view the documentation of the PostModel. ApiResponse Let's use the @ApiResponse() decorator for Swagger to document all the possible responses from our API's endpoints. This will be helpful for informing users of our API what responses they can expect to receive by calling a given endpoint. We will be making these changes in the PostsController class. For the findAll method, let's use the @ApiOkResponse() decorator to document a 200 OK success response. ` For the findOne method, let's use the @ApiOkResponse() decorator to document a 200 OK response when the post is found. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when a post is not found. ` For the create method, let's use the @ApiCreatedResponse() decorator to document a 201 CREATED response when a post is created. Let's use the @ApiUnprocessableEntityResponse() decorator to document a 422 UNPROCESSABLE ENTITY HTTP error when a duplicate post title is found. ` For the delete method, let's use the @ApiOkResponse() decorator to document a 200 OK response when a post is deleted. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when the post to be deleted is not found. ` For the update method, let's use the @ApiOkResponse() decorator to document a 200 OK response when a post is updated. Let's use the @ApiNotFoundResponse() decorator to document a 404 NOT FOUND HTTP error when the post to be updated is not found. Let's use the @ApiUnprocessableEntityResponse() decorator to document a 422 UNPROCESSABLE ENTITY HTTP error when a duplicate post title is found. ` After saving these changes, all response codes and their related descriptions should now be listed for each endpoint on the Swagger web page at http://localhost:3000/api. We could also use Swagger instead of Insomnia to test our API. By clicking on the "Try it out" button that appears under each endpoint on the Swagger web page, we can see if they are working as expected. Exception filter Exception filters give us full control over the exceptions layer of NestJS. We can use an exception filter to add custom fields to a HTTP exception response body or to print out logs of every HTTP exception that occurs to the Terminal. Let's create a new filters folder in the /src folder, as well as a new file called http-exception.filter.ts within it. Let's add the following class in this file. ` This class makes use of the NestJS logger to print a warning message to the Terminal whenever a HTTP exception occurs. It also returns two custom fields within the response body whenever a HTTP exception occurs. The timestamp field reports when the exception occurred, and the endpoint field reports which route triggered the exception. In order to apply this filter, we need to decorate the PostsController with @UseFilters(new HttpExceptionFilter()). ` After saving these changes, NestJS will reload our application for us. If we use Insomnia to send a PUT /posts/1 request to our API, it should trigger a 404 NOT FOUND HTTP error, since no blog posts exist for updating in our application when it starts. The HTTP exception response body that is returned to Insomnia should now contain the timestamp and endpoint fields. ` We should also see the following line printed out to the Terminal. ` Summary In this article, we saw how NestJS makes back-end API development fast, simple, and effective. The NestJS application structure has helped us build a very well-organized project. We covered a lot, so let's recap what we learned: - How to use the NestJS CLI. - How to build feature modules in NestJS. - How to use services and controllers. - How to test an API with Insomnia. - How to add logging to NestJS apps. - How to use Swagger for documenting and previewing NestJS APIs. - How to get full control of HTTP exceptions in NestJS. Happy developing with NestJS! Code Visit the following GitHub repository in order to access the code featured in this article....

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website cover image

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website

Lessons from the DOGE Website Hack: How to Secure Your Next.js Website The Department of Government Efficiency (DOGE) launched a new website, doge.gov. Within days, it was defaced with messages from hackers. The culprit? A misconfigured database was left open, letting anyone edit content. Reports suggest the site was built on Cloudflare Pages, possibly with a Next.js frontend pulling data dynamically. While we don’t have the tech stack confirmed, we are confident that Next.js was used from early reporting around the website. Let’s dive into what went wrong—and how you can secure your own Next.js projects. What Happened to DOGE.gov? The hack was a classic case of security 101 gone wrong. The database—likely hosted in the cloud—was accessible without authentication. No passwords, no API keys, no nothing. Hackers simply connected to it and started scribbling their graffiti. Hosted on Cloudflare Pages (not government servers), the site might have been rushed, skipping critical security checks. For a .gov domain, this is surprising—but it’s a reminder that even big names can miss best practices. It’s easy to imagine how this happened: an unsecured server action is being used on the client side, a serverless function or API route fetching data from an unsecured database, no middleware enforcing access control, and a deployment that didn’t double-check cloud configs. Let’s break down how to avoid this in your own Next.js app. Securing Your Next.js Website: 5 Key Steps Next.js is a powerhouse for building fast, scalable websites, but its flexibility means you’re responsible for locking the doors. Here’s how to keep your site safe. 1. Double-check your Server Actions If Next.js 13 or later was used, Server Actions might’ve been part of the mix—think form submissions or dynamic updates straight from the frontend. These are slick for handling server-side logic without a separate API, but they’re a security risk if not handled right. An unsecured Server Action could’ve been how hackers slipped into the database. Why? Next.js generates a public endpoint for each Server Action. If these Server Actions lack proper authentication and authorization measures, they become vulnerable to unauthorized data access. Example: * Restrict Access: Always validate the user’s session or token before executing sensitive operations. * Limit Scope: Only allow Server Actions to perform specific, safe tasks—don’t let them run wild with full database access. * Don’t use server action on the client side without authorization and authentication checks 2. Lock Down Your Database Access Another incident happened in 2020. A hacker used an automated script to scan for misconfigured MongoDB databases, wiping the content of 23 thousand databases that have been left wide open, and leaving a ransom note behind asking for money. So whether you’re using MongoDB, PostgreSQL, or Cloudflare’s D1, never leave it publicly accessible. Here’s what to do: * Set Authentication: Always require credentials (username/password or API keys) to connect. Store these in environment variables (e.g., .env.local for Next.js) and access them via process.env. * Whitelist IPs: If your database is cloud-hosted, restrict access to your Next.js app’s server or Vercel deployment IP range. * Use VPCs: For extra security, put your database in a Virtual Private Cloud (VPC) so it’s not even exposed to the public internet. If you are using Vercel, you can create private connections between Vercel Functions and your backend cloud, like databases or other private infrastructure, using Vercel Secure Compute Example: In a Next.js API route (/app/api/data.js): ` > Tip: Don’t hardcode MONGO_URI—keep it in .env and add .env to .gitignore. 3. Secure Your API Routes Next.js API routes are awesome for server-side logic, but they’re a potential entry point if left unchecked. The site might’ve had an API endpoint feeding its database updates without protection. * Add Authentication: Use a library like next-auth or JSON Web Tokens (JWT) to secure routes. * Rate Limit: Prevent abuse with something like rate-limiter-flexible. Example: ` 4. Double-Check Your Cloud Config A misconfigured cloud setup may have exposed the database. If you’re deploying on Vercel, Netlify, or Cloudflare: * Environment Variables: Store secrets in your hosting platform’s dashboard, not in code. * Serverless Functions: Ensure they’re not leaking sensitive data in responses. Log errors, not secrets. * Access Controls: Verify your database firewall rules only allow connections from your app. 5. Sanitize and Validate Inputs Hackers love injecting junk into forms or APIs. If your app lets users submit data (e.g., feedback forms), unvalidated inputs could’ve been a vector. In Next.js: * Sanitize: Use libraries like sanitize-html for user inputs. * Validate: Check data types and lengths before hitting your database. Example: ` Summary The DOGE website hack serves as a reminder of the ever-present need for robust security measures in web development. By following the outlined steps–double-checking Server Actions, locking down database access, securing API routes, verifying cloud configurations, and sanitizing/validating inputs–you can enhance the security posture of your Next.js applications and protect them from potential threats. Remember, a proactive approach to security is always the best defense....

CSS Container Queries, what are they? cover image

CSS Container Queries, what are they?

CSS Container queries, what are they? Intro Media queries have always been crucial to building web applications. They help make our apps more accessible and easier to use and ensure we reach most of our audience. Media queries have been essential in frontend development to create unique user interfaces. But now, there’s something new: Container queries. In this blog post, we’ll explore what Container queries are, how they differ from media queries, and why they’re so amazing. So, let’s get started! Refresh on Media queries Media queries have been available in browsers for a long time, but they didn’t become popular until around 2010 when mobile devices started to take off. Media queries let us add specific styles based on the type of device, like screens or printers. This is especially helpful for creating modern, responsive apps. A simple use of Media queries would be changing, for example, a paragraph's font size when the screen width is less than a specific number. ` In this simple example, when the browser’s viewport width is less or equal to 400px, the font size changes to 8px. Notice how straightforward the syntax is: we start with the keyword @media, followed by the type of device it should apply to. In this case, we use screen so it doesn’t affect users who print the page—if you don’t add anything, then it falls back to the default, which is “all” including both print and screen. Then we specify a media feature, in this case, the width. Container queries Container queries are similar to Media queries. Their main function is to apply styles under certain conditions. The difference is that instead of listening to the viewport of the browser, it listens to a container size. Let’s see this example: In the above layout, we have a layout with a sidebar and three cards as the content. Using Media queries we could listen to the viewport width and change the layout depending on a specific width. Like so: ` That’s acceptable, but it requires us to constantly monitor the layout. For example, if we added another sidebar on the right (really weird, but let’s imagine that this is a typical case), our layout would become more condensed: We would need to change our media queries and adjust their range in this situation. Wouldn’t it be better to check the card container’s width and update its styles based on that? That way, we wouldn’t need to worry about if the layout changes, and that’s precisely what container queries are made for! First, to define the container we are going to listen to, we are going to add a new property to our styles: ` The .container class is the one in which our cards reside. By adding the property `container-type, ' we now define this class as a container we want to listen to. We said inline-size as the value to query based on the inline dimensions of the container because we just want to listen to the element's width. The value of container-type will depend on your use case. If you want to listen to both width and height, then size will be a better fit for you. You can also have normal as your container-type value, which means the element won’t act as a query container at all. This is handy if you need to revert to the default behavior. Next, to define our query, we use the new @container CSS at-rule: ` Notice that it is really similar to how we define our Media queries. Now, if we look at the same screen, we will see the following: This is very powerful because we can now style each component with its own rules without changing the rules based on the layout changes. The @container will affect all the defined containers in the scope; we might not want that. We can define the name of our container to specify that we only want to listen to that in specific: ` We can also have a shorthand to define our container and its name: ` Container query length units Container query lengths are similar to the viewport-percentage length units like vh or vw units, but instead of being relative to the viewport, they are to the dimensions of the query container. We have different units, each relative to different dimensions of the container: - cqw: 1% of a query container's width - cqh: 1% of a query container's height - cqi: 1% of a query container's inline size - cqb: 1% of a query container's block size - cqmin: The smaller value of either cqi or cqb - cqmax: The larger value of either cqi or cqb In our example, we could use them to define the font size of our cards: ` Using these units alone isn’t recommended because they’re percentage-based and can have a value we don’t want. Instead, it’s better to use a dynamic range. Using the max function, we can set 2 values and always pick the highest one. Conclusion Container queries bring a fresh and powerful approach to web design but are not meant to replace Media queries. I think their real power shines when used together. Media queries often require constant adjustments as your layout evolves. Container queries, however, let you style individual components based on their dimensions, making the designs more flexible and easier to manage. Adding a new component or rearranging elements won’t force us to rewrite our media queries. Instead, each component handles its styling, leading to cleaner and more organized code. Please note that, as of writing this blog post, they aren’t compatible with all browsers yet. Take a look at this table from caniuse.com: A good fallback strategy for this, when hitting an unsupported browser would be the use of the @support rule, which allows you to apply styles only if the browser supports the CSS feature. For example: ` Ensure your media queries are good enough to keep everything responsive and user-friendly when the condition is unmet. Thank you for reading! Enjoy the extra flexibility that container queries bring to your web designs. Check out a live demo to see it in action. Happy styling!...

Let's innovate together!

We're ready to be your trusted technical partners in your digital innovation journey.

Whether it's modernization or custom software solutions, our team of experts can guide you through best practices and how to build scalable, performant software that lasts.

Prefer email? hi@thisdot.co