DEV Community

Preston Lamb
Preston Lamb

Posted on • Originally published at prestonlamb.com on

Share Interfaces in Angular and NestJS with Nx

tldr;

I'm a big fan of using Nx and having multiple apps in a single workspace, especially when the apps have a lot in common and a lot of code to share. This is especially helpful when dealing with interfaces for data retrieved from or sent to the server. In this article, we'll talk about how to share those interfaces between the front and back end.

This isn't the only way to manage these interfaces, but it is working well for us on a full stack project in our Nx workspace. An update can be made to the interface in one spot, and the front and back ends stay in sync. I've worked on a lot of projects where the communication breaks down and it's unclear what data should be sent or expected. That stumbling block can be eliminated by having your front and back end apps in the same workspace.

Setup

If you want to follow along, create a new Nx workspace:

$ npx create-nx-workspace
Enter fullscreen mode Exit fullscreen mode

Give the workspace a name, and then select the angular-nest option for creating the workspace. After that you can enter what you want for the remainder of the prompts. Once the dependencies are installed, you should be good to go.

When you open the project, you'll see a couple directories inside the apps directory. The three directories are api, your Angular app folder, and the Angular app's end to end test folder. In the libs directory is an api-interfaces lib.

Creating an Interface

Let's create our first interface. It'll be a simple example, one that's frequently used: a todo. In the api-interfaces lib, create a new file called todo.interface.ts next to the api-interfaces.ts file that was created automatically when the workspace was created. Put the following contents in the new file:

// todo.interface.ts

export interface Todo {
    id: number
    title: string;
    complete: boolean;
}
Enter fullscreen mode Exit fullscreen mode

This will be the base interface for the todos in our app. The Angular app will use this interface for type checking, and the API will implement this interface for the entities and DTOs that are used in NestJS.

Creating a DTO

Now that we have an interface, let's create a DTO that our NestJS app can use to define the data that should be sent when creating a new todo or updating an existing one. In the api app, create a new file: todo.dto.ts and add the following:

// create-todo.dto.ts

import { Todo } from '@my-workspace/api-interfaces

export class CreateTodoDto implements Omit<Todo, 'id'> {
    title: string;
    completed: boolean;
}
Enter fullscreen mode Exit fullscreen mode

Note: in all likelihood you will not create this DTO in the api app, but in a new lib that you create for this NestJS module. To keep things simple for this demo, though, I selected the api folder to contain the DTOs and entities.

There are a couple things I want to point out here. First, we import our Todo interface from the api-interfaces lib, the one we created in the last section. This is the base for the DTO. We implement the interface to create our CreateTodoDto, but we use the Omit utility type to remove the id attribute from the DTO. The reason for this is because we won't have an id for the todo when we're creating it; that will be determined by the database. By using the Omit utility type and removing the id attribute from the DTO, we don't need to pass a null id when creating a new todo while at the same time requiring all the other attributes.

With this DTO now created, we can tell the controller what type to expect when a new todo is created. If an object with more or less attributes is passed to the endpoint, we can return a 400 with the required fields on the object.

Creating an Entity

Creating the entity is similar to the DTO, but we won't need to use the Omit utility type. Here's an example of the entity:

// todo.entity.ts
import { Todo } from '@my-workspace/api-interfaces';
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity('todo')
export class TodoEntity implements Todo {
    @PrimaryGeneratedColumn()
    id: number;

    @Column()
    title: string;

    @Column()
    completed: boolean;
}
Enter fullscreen mode Exit fullscreen mode

The implementation here is very similar to the DTO, but we don't leave out the id. Instead, we mark it as the primary key and set it to be auto generated when a new todo is created. The other difference is the @Entity() decorator. This is required by typeorm to make this a table in the database where todos can be saved. Also, because the class is defined as TodoEntity, the table would be called todo_entity by default. By providing a string inside the parentheses for the Entity decorator, we can set the table name to todo.

I like to call the entity TodoEntity so that it's clear when I'm using it in different files that I am not using the interface. There are other ways, however, to distinguish between the two files and objects. Do whatever feels best to you!

The Benefits

So we just created three files for todos in our app. What's the benefit? Well, Angular is more powerful when types or interfaces or classes are used to determine how the data in your app is organized. You get auto completion from your IDE, and you're less likely to use the wrong attribute or method. In addition, with NestJS as our backend, we can easily check that the data that's sent to the backend is shaped correctly so that we don't have server errors popping up because too many attributes (or not enough) are sent along with the request.

The hard part is when you want or need these files on both the front end and the back end. Many times, those applications are in different repositories and managed by different teams. By having both applications in the same workspace, we've closed that gap. And, by having the DTO and entity implement the interface, we're assured that if the interface changes then changes will need to be made to the DTO and entity. Those two files will have errors if attributes are added or removed to the interface and they don't implement the right attributes. Thus, the shape of the data is always the same on the front and back end.

Conclusion

I hope this article helped you with knowing how you can manage the interfaces, DTOs and entities in your application. When starting this project, I was unsure of the best way to do this same thing. I got some ideas from Twitter, and then just started trying it out and came up with this method. I know there are other ways to do it; maybe even better ways. But this was a simple implementation and has worked great so far. Let me know how it goes for you

Discussion (0)