# Full Stack Type Safety with Angular, Nest, Nx, and Prisma URL: https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc Published: 2021-01-19 Updated: 2026-07-10 Authors: Ryan Chenkie Tags: education Description: A 2021 tutorial on sharing Prisma 2 types across an Angular and NestJS app in an Nx monorepo. The technique still applies; commands and APIs have changed. This 2021 tutorial builds a small ecommerce app with Angular, NestJS, and Prisma 2 inside an Nx monorepo, sharing the types [Prisma](https://www.prisma.io/docs/orm) generates across the whole stack: database, API, and frontend. > **Updated (July 2026):** This tutorial targets the stack as it existed in early 2021: NgModule-based Angular, NestJS, the `@nrwl/*` Nx plugins, and Prisma 2. Each has shipped several major versions since. The core technique of sharing Prisma-generated types across an Nx monorepo still works, but individual commands and APIs below have changed. For a current setup, start with the [Prisma ORM docs](https://www.prisma.io/docs/orm) and pair the ORM with [Prisma Postgres](https://www.prisma.io/docs/postgres) for the database. For NestJS and Prisma on current versions, follow the updated [Building a REST API with NestJS and Prisma](https://www.prisma.io/blog/nestjs-prisma-rest-api-7D056s1BmOL0) series. 2020 was a great year for TypeScript. Usage surged, developers came to love the benefits of type safety, and the language saw adoption in many different settings. While TypeScript might be fairly new to developers who mainly work with React, Vue, Svelte, and others, it has been around for quite some time for Angular developers. Angular (version 2+) was initially authored in 2015 and was done so using TypeScript. The Angular team made an early bet on type safety, encouraging developers to write their Angular apps in TypeScript as well, even though writing them in JavaScript was an option. Many Angular developers were initially resistant. TypeScript wasn't very mature in 2015 and there was a steep learning curve. It was common to be slowed down by environment incompatabilities and bugs. All around, there was often a great deal of frustration. Fast-forward to 2021 and Angular developers have been enormously successful using TypeScript. Teams have benefited greatly from type safety over the years. While type safety for Angular applications is nothing new, it's less common for Angular developers who work across the full stack. Frameworks like NestJS have made it easy to use TypeScript in a Node environment, but one spot that has continued to lack is at the database. Several tools now exist for acheiving type-safe database access, [Prisma](https://www.prisma.io/) being one of them. In this article, we'll look at how we can use the types generated by Prisma to apply type safety to all parts of an Angular and Nest ecommerce application. We'll work in an Nx monorepo so that we can easily import type across the whole stack. Let's get started! Check out the [code for the project on GitHub](https://github.com/chenkie/shirt-shop). ![Products from ShirtShop](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-10.png) ## Create an Nx Workspace [#create-an-nx-workspace] One of the easiest ways to share types between a front end and backend project is to house everything under a monorepo. [Nx Dev Tools](https://nx.dev) (created by [Nrwl](https://twitter.com/nrwl_io)) makes working with monorepos simple. Nx stipulates a set of conventions that, when followed, allow for simplicity when maintaining multiple applications under a single repository. Let's start by creating an Nx workspace for our project. We'll use the `create-nx-workspace` command to do so. In a terminal window, create a workspace with a preset of `angular`. ```shell npx create-nx-workspace --preset=angular ``` An interactive prompt takes us through the setup process. Select a name for the workspace and application and then continue through the prompts. ![Interactive prompts for setting up an Nx workspace](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-1.png) Once Nx finishes wiring up the workspace, open it up and try running the Angular application. ```shell npm start ``` This command will tell Nx to serve the Angular application that was created as the workspace initialized. After it compiles, open up `localhost:4200` to make sure everything is working. ![The Angular application running on localhost](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-2.png) ## Add a NestJS Application [#add-a-nestjs-application] Our front end is ready to go but we haven't yet included a project for the backend. Let's add a NestJS project to the workspace. To add our NestJS project, we first need to install the official NestJS plugin for Nx. In a new terminal window, grab the `@nrwl/nx` package from npm. ```shell npm install -D @nrwl/nest ``` After installation, use the plugin to generate a NestJS project within the workspace. Since we'll only have one backend project for this example, let's just name it "api". ```shell nx generate @nrwl/nest:application api ``` Once the generator finishes, we can see a new folder called `api` under the `apps` directory. This is where our NestJS app lives. The default NestJS installation comes with a single endpoint which returns a "hello world" message. Let's start the API and make sure we can access the endpoint. To start the API, target the `nx serve` command directly at the NestJS app. ```shell nx serve api ``` Once the API is up and running, go to `http://localhost:3333/api` in the browser and make sure you can see the "hello world" message. ![The NestJS application running on localhost](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-3.png) ## Install Prisma and Set Up a Database [#install-prisma-and-set-up-a-database] Now that we've got our front end and backend projects in place, let's set up Prisma so we can start writing some code! We need to install two packages to work with Prisma: the Prisma Client (as a regular dependency) and the Prisma CLI (as a dev dependency). ```shell npm install @prisma/client npm install -D @prisma/cli ``` > **Note**: The `@prisma/cli` package used here was renamed to `prisma` in Prisma 2.16.0 (February 2021), two weeks after this article was published. On current versions, install `prisma` as the dev dependency instead. The Prisma Client is what gives us ORM-style type-safe database access in our code. The Prisma CLI is what gives us a set of commands to initialize Prisma, create database migrations, and more. With those packages installed, let's initialize Prisma. ```shell npx prisma init ``` After running this command, a `prisma` directory is created at the workspace root. Inside is a single file called `schema.prisma`. This file uses the [Prisma Schema Language](https://www.prisma.io/docs/orm/v7/prisma-schema/overview) and is the place where we define the shape of our database. We use it to describe the tables for our databases and their columns, the relationships between tables, and more. When we create a Prisma model, we need to select a `provider` for our datasource. The default `schema.prisma` file comes with a datasource called `db` which uses PostgreSQL as the provider. Instead of using Postgres, let's use SQLite so we can keep things simple. Switch up the `db` datasource so that uses SQLite. Point the `url` parameter to a file called `dev.db` within the filesystem. ```prisma datasource db { provider = "sqlite" url = "file:./dev.db" } ``` **Note:** We don't need to create the `dev.db` file ourselves. Its creation will be taken care of for us in a later step. Let's now set up a simple model for our shop. To get ourselves started, let's work with a single table called `Product`. To do so, create a new `model` in the schema file and give it some fields. ```prisma model Product { id String @id @default(cuid()) name String description String image String price Int sku String } ``` The `id` field is marked as the primary key via the `@id` directive. We're also setting its default value to be a collision-resistant unique ID. The other fields and fairly straight-forward in their purpose. With the model in place, let's run our first migration so that the filesystem database file gets created and populated with our `Product` table. ```shell npx prisma migrate dev --preview-feature ``` > **Note**: Prisma Migrate was in preview when this article was written. It became [generally available in Prisma 2.19](https://www.prisma.io/blog/prisma-migrate-ga-b5eno5g08d0b) (March 2021), and the `--preview-feature` flag no longer exists. On current versions the command is `npx prisma migrate dev`. An interactive prompt will ask for the name of the migration. Call it whatever you like, something like `init` works fine. After the migration completes, a `dev.db` file is created in the `prisma` directory, along with a `migrations` directory. It's within the `migrations` directory that all of the SQL that's used to perform our database migrations is stored. Since these files are raw SQL, we have the opportunity to adjust them before they operate on our databases. Read the [migrate docs](https://www.prisma.io/docs/orm/v7/prisma-migrate) to find out more about how you can customize the migration behavior. ## View the Database with Prisma Studio and Seed Some Data [#view-the-database-with-prisma-studio-and-seed-some-data] With the database in place and populated with a table, we can now take a look at it and add some data using [Prisma Studio](https://www.prisma.io/studio). Prisma Studio is a GUI for viewing and managing our databases and runs in the browser. In a new terminal window, use the Prisma CLI to fire up Prisma Studio. ```shell npx prisma studio ``` Running this command will open Prisma Studio. In the browser, it opens at `localhost:5555`. ![Prisma Studio running at localhost](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-4.png) We can use Prisma Studio to add data to the database manually. This isn't a great approach if we have a lot of data to seed, but it's useful if we want to add a few records to test with. Add as many rows as you like and input data for them. If you would like to work with the data seen in this article, you can grab it in [this gist](https://gist.github.com/chenkie/9bd80bc2767e72f71582a02e05c7f853). ![New rows in Prisma Studio](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-5.png) Next, save the changes. IDs for each row will automatically be generated. ![Saved data in Prisma Studio](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-6.png) We now have all the pieces of our stack in place! We're ready to start writing some code to surface the data from the API and call for it from the Angular app. ## Create a Products Controller for the API [#create-a-products-controller-for-the-api] The data in our database is ready to go. What we need now is an endpoint we can call to retreive it. To make this happen, we'll create a library for our NestJS controller and a service that we can reach into to expose an endpoint that responds to `GET` requests. Use the NestJS Nx plugin to generate a new library called `products`. Include a controller and a service within. ```shell nx generate @nrwl/nest:library products --controller --service ``` We'll create a method in the service to reach into our database to get the data. Then, in the controller, we'll expose a `GET` endpoint which uses the service to get that data and return it to the client. Let's start by building out the database query within the service. This is the first spot where Prisma's types shine. Within `products.service.ts`, import `PrismaClient`, create an instance of it, and expose a `public` method to query for the data. ```ts // libs/products/src/lib/products.service.ts import { Injectable } from '@nestjs/common' import { PrismaClient, Product } from '@prisma/client' const prisma = new PrismaClient() @Injectable() export class ProductService { public getProducts(): Promise { return prisma.product.findMany() } } ``` We're importing two things from `@prisma/client` here: `PrismaClient` and `Product`. `PrismaClient` is what we use to create an instance of our database client and it exposes methods and properties that are useful for querying the database. The `Product` import is the TypeScript type that was generated for us by Prisma when we ran our database migrations. This type has the shape of our `Product` table and is useful for informing consumers of the `getProducts` method about what it can expect the returned data to look like. **Note:** We're instantiating `PrismaClient` directly within our `ProductsService` file here. In a real world application, we should instead create a dedicated file for this instance. That way, we wouldn't need to instantiate it multiple times. Let's now work within the controller to make a call to `getProducts` to fetch the data. Open up `products.controller.ts` and add a method which responds to `GET` requests. ```ts // libs/products/src/lib/products.controller.ts import { Controller, Get } from '@nestjs/common' import { ProductsService } from './products.service' @Controller('products') export class ProductController { constructor(private productService: ProductsService) {} @Get() public getProducts() { return this.productService.getProducts() } } ``` We've applied the `getProducts` method with the `@Get` decorator which means when we make a `GET` request to `/products`, the method will be run. The method itself reaches into the service to get the data. Before we can test out this endpoint, we need to add `ProductsController` and `ProductsService` in the main module for the `api`. Open up `app.module.ts` found within `apps/api/src/app` and import `ProductsController` and `ProductsService`. Then include them in the `controllers` and `providers` arrays respectively. ```ts // apps/api/src/app/app.module.ts import { Module } from '@nestjs/common' import { AppController } from './app.controller' import { AppService } from './app.service' import { ProductsController, ProductsService } from '@shirt-shop/products' @Module({ imports: [], controllers: [AppController, ProductsController], providers: [AppService, ProductsService], }) export class AppModule {} ``` Now head over to the browser and test it out by going to `http://localhost:3333/api/products`. ![Products data from the API](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-7.png) It may not be very apparent at this point, but our endpoint has a layer of type safety applied to it that can help us out if we need to manipulate and/or modify data before it is returned to the client. For example, if we need to map over our data and get access to its properties, we now have full autocompletion enabled when we do so. This occurs because we told the `getProducts` method in the `ProductsService` that the return type is a `Promise` that resolves with an array of type `Product`. ![Autocompletion on the Product type](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-8.png) Now that we have the API working, let's wire up the Angular application to make a call for this data and display it! ### Enable CORS [#enable-cors] When we create our NestJS API, we have the option of setting up a proxy for our frontend applications such that both the front end and backend get served over the same port. This is useful for situations where we don't want to have separate domains for the two sides of the app. Instead of setting up a proxy for this demo, we can instead enable CORS on the backend so that our front end can make calls to it. We won't need this until later, but let's get it set up and out of the way now. Open up `apps/api/src/main.ts` and add a call to \`app.enableCors(); ```ts // apps/api/src/main.ts import { Logger } from '@nestjs/common' import { NestFactory } from '@nestjs/core' import { AppModule } from './app/app.module' async function bootstrap() { const app = await NestFactory.create(AppModule) const globalPrefix = 'api' app.setGlobalPrefix(globalPrefix) app.enableCors() const port = process.env.PORT || 3333 await app.listen(port, () => { Logger.log('Listening at http://localhost:' + port + '/' + globalPrefix) }) } bootstrap() ``` ## Create a UI Module for the Angular App [#create-a-ui-module-for-the-angular-app] We could just start building components directly within the `shirt-shop` app in our Nx workspace, but that would be against the advice that Nx gives about how to manage code in our monorepos. Instead, let's create a new module that will be dedicated to components that make up our UI. Head over to the command line and create a new module. Follow the prompts to select the desired CSS variety. ```shell nx generate @nrwl/angular:lib ui ``` Once the module is in place, we can create a component to list our products as well as a service to make the API call to get the data. Let's start by generating a component. ```shell nx g component products --project=ui --export ``` Using the `--project=ui` flag tells Nx that we want to put this component in our newly-created `ui` module. We can see the result under `/libs/ui/src/lib/products`. Let's now create a service. ```shell nx g service product --project=ui --export ``` With the new `UiModule` in place, we now need to add it to the `imports` array in our `app.module.ts` file for the frontend. ```ts // apps/shirt-shop/src/app/app.module.ts import { BrowserModule } from '@angular/platform-browser' import { NgModule } from '@angular/core' import { AppComponent } from './app.component' import { UiModule } from '@shirt-shop/ui' @NgModule({ declarations: [AppComponent], imports: [BrowserModule, UiModule], providers: [], bootstrap: [AppComponent], }) export class AppModule {} ``` **Note:** If you get any errors saying that `@shirt-shop/ui` cannot be found, try restarting the front end by stopping that process and running `nx serve` again. ## Add an API Call to the Service [#add-an-api-call-to-the-service] We'll use Angular's built-in `HttpClientModule` to get access to an HTTP client for making requests to the API. To get started, let's import the appropriate module. The place to do this is within the `ui.module.ts` file in our new `UiModule`. ```ts // libs/ui/src/lib/ui.module.ts import { NgModule } from '@angular/core' import { CommonModule } from '@angular/common' import { ProductsComponent } from './products/products.component' import { HttpClientModule } from '@angular/common/http' @NgModule({ imports: [CommonModule, HttpClientModule], declarations: [ProductsComponent], exports: [ProductsComponent], }) export class UiModule {} ``` We can now import Angular's `HttpClient` within our `ProductService` and make calls with it. ```ts // libs/ui/src/lib/product.service.ts import { HttpClient } from '@angular/common/http' import { Injectable } from '@angular/core' import { Product } from '@prisma/client' import { Observable } from 'rxjs' @Injectable({ providedIn: 'root', }) export class ProductService { private API_URL: string = 'http://localhost:3333/api' constructor(private readonly http: HttpClient) {} public getProducts(): Observable { { return this.http.get(`${this.API_URL}/products`) } } } ``` Notice that we're using the same `Product` type that gets exported from `@prisma/client` here within our `ProductService` that was used on the backend in the `ProductsController`. This is a great illustration of how we can benefit from using the same types across our whole stack. When we use the `getProducts` method from this service, we'll now have type safety applied. ## Build Out the Products Component [#build-out-the-products-component] We're now ready to add some structure and style to our `ProductsComponent` so we can display the products to our users. Let's start by adding some CSS that will style our component. Open up `libs/ui/src/lib/products/product.component.css` and add the following styles: ``` /* libs/ui/src/lib/products/product.component.css */ :host { display: grid; gap: 40px; grid-template-columns: repeat(3, 33% [col-start]); } .product-card { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); background-color: #fff; border-radius: 15px; padding: 15px; } .product-card img { border-radius: 15px; max-width: 100%; height: 200px; display: block; margin: 0 auto; } .product-name { font-weight: bold; font-size: 22px; } .product-description { color: rgb(122, 122, 122); } .product-price { font-weight: bold; font-size: 24px; } .add-to-cart-button { background: rgb(49, 175, 255); background: linear-gradient(90deg, rgba(49, 175, 255, 1) 0%, rgba(0, 123, 252, 1) 100%); padding: 10px 20px; border-radius: 30px; border: none; color: rgb(219, 233, 248); cursor: pointer; box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); } ``` Next, open up `libs/ui/src/lib/products/product.component.html` and add the structure for products to be displayed.. ```html

{{ product.name }}

{{ product.description }}

{{ product.price | currency }}

``` Finally, we need to add a method to the component class which uses the `ProductService` to get the data. We'll then put the result on the `$products` observable that we've already stubbed out in our template above. ```ts // libs/ui/src/lib/products/products.component.ts import { Component, OnInit } from '@angular/core' import { ProductService } from '../product.service' import { Observable } from 'rxjs' import { Product } from '@prisma/client' @Component({ selector: 'shirt-shop-products', templateUrl: './products.component.html', styleUrls: ['./products.component.css'], }) export class ProductsComponent implements OnInit { public $products: Observable constructor(public productService: ProductService) {} ngOnInit(): void { this.$products = this.productService.getProducts() } } ``` This is another spot where we're using our `Product` type from `@prisma/client` to give ourselves type safety. Applying this type directly to the `$products` observable means that we can get autocompletion in our Angular templates. ![Autocompletion in the Angular template](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-9.png) With our component in place, we're now ready to call it from the `shirt-shop` app and display the results! Open up `apps/shirt-shop/src/app/app.component.html` and include the `Products` component. ```html

Welcome to Shirt Shop!

``` ![Products from ShirtShop](https://www.prisma.io/blog/full-stack-typesafety-with-angular-nest-nx-and-prisma-CcMK7fbQfTWc/imgs/nx-prisma-10.png) ## Going Beyond Displaying Data [#going-beyond-displaying-data] For any real-world applicaton, we no doubt need a way to take user input and create records in the database. We won't build out a full CRUD experience for this demonstration, but we can take a quick look at some of the features from the `PrismaClient` that would help us store new data. Let's say we have a section in our app which allows admins to add new products in. We'd likely want to start by creating an endpoint to receive this data and store it. In this case, we could use the `create` method on `PrismaClient` along with the `ProductCreateInput` type that is exposed on a top-level export called `Prisma`. ```ts import { Injectable } from '@nestjs/common' import { PrismaClient, Product, Prisma } from '@prisma/client' const prisma = new PrismaClient() @Injectable() export class ProductService { // ... public createProduct(data: Prisma.ProductCreateInput): Promise { return prisma.product.create({ data, }) } } ``` The `createProduct` method takes in some data which is type-hinted to abide by the `Product` model from our Prisma schema. The returned result is a single `Product` that gets resolved from a `Promise`. It should be noted that just type-hinting our `data` parameter here doesn't do anything to add real validation to this endpoint. For data validation at the endpoint, we need to use [Validation Pipes](https://docs.nestjs.com/techniques/validation) from NestJS. ## Wrapping Up [#wrapping-up] TypeScript has come a long way since its early days and early adoption in the Angular community. Using TypeScript on both the frontend and backend bodes well for developer experience and confidence. Applying type safety to database access goes one step further in providing teams large and small with a slew of benefits. Wrapping the whole application up in a monorepo like those provided by Nx gives us an easy way of reusing code (including type definitions) across the whole stack. If you'd like to go even further with Prisma, [check out the docs](https://www.prisma.io/docs), follow us on [X/Twitter](https://pris.ly/x), and join our [Discord community](https://pris.ly/discord)!