
Achieving Type Safety Across Angular and NestJS with Prisma's New Generator
Table of Contents
Table of Contents
One of the most exciting challenges in full-stack development is maintaining type safety across your entire application. When you're working with databases, backend APIs, and frontend interfaces, keeping your types synchronized can become a nightmare. But what if I told you there's a way to generate types once and use them everywhere?
Prisma has introduced a new generator since version 6.x that changes the game completely. This new generator gives us something more tangible about where our types and Prisma client come from, addressing the fact that modifying node_modules (except through npm/yarn) isn't exactly a best practice.
In this guide, I'll show you how to leverage this new Prisma generator to share types between your NestJS backend and Angular frontend using Nx as our monorepo tool.
But, what makes this new generator so cool?
The new Prisma generator creates types and enums outside of node_modules, which means we can actually reuse these types across our entire stack. This is huge because it means:
Type Safety Everywhere: Your database schema becomes the single source of truth for your entire application
No More Duplication: Stop writing the same interfaces in your backend and frontend
Better Developer Experience: IntelliSense and autocomplete work seamlessly across your stack
Reduced Bugs: Catch type mismatches at compile time, not runtime
In simple terms, this new generator helps you:
Generate Reusable Types: Create TypeScript interfaces and enums that can be shared across projects
Maintain Consistency: Ensure your frontend and backend are always in sync with your database schema
Improve Productivity: Spend less time writing boilerplate types and more time building features
Scale Better: As your application grows, your types grow with it automatically
Setting Up Our Project
We'll use Nx to create a monorepo that contains:
A shared library with our Prisma-generated types
A NestJS backend API
An Angular frontend application
By the end of this post, you'll have a complete working example available on GitHub that demonstrates this powerful pattern in action.
Let's start by creating our Nx workspace:
npx create-nx-workspace@latest
When prompted, use these configuration options:
✔ Where would you like to create your workspace? · prisma-nest-angular-workspace
✔ Which stack do you want to use? · angular
✔ Integrated monorepo, or standalone project? · integrated
✔ Application name · blog-app
✔ Which bundler would you like to use? · esbuild
✔ Default stylesheet format · scss
✔ Do you want to enable Server-Side Rendering (SSR) and Static Site Generation (SSG/Prerendering)? · No
✔ Which unit test runner would you like to use? · vitest
✔ Test runner to use for end to end (E2E) tests · none
✔ Which CI provider would you like to use? · skip
✔ Would you like remote caching to make your build faster? · skip
Great! Now we have our Angular application set up. Next, let's add NestJS to our monorepo.
Adding NestJS to Our Monorepo
First, we need to install the Nx NestJS plugin:
npx nx add @nx/nest
Now we can generate our NestJS application. You can do this either through the VS Code Nx extension or via the command line:
npx nx generate @nx/nest:application --directory=apps/blog-api --linter=eslint --name=blog-api --strict=true --unitTestRunner=jest --no-interactive
Perfect! Now we have both applications in our monorepo:
Angular frontend at
apps/blog-appNestJS backend at
apps/blog-api
Creating a Shared Library for Prisma Types
Now comes the exciting part! We need to create a shared library that will house our Prisma-generated types. This library will be the central place where both our Angular and NestJS applications can import types from.
Let's create our shared library:
npx nx generate @nx/js:library --directory=libs/prisma-generated --bundler=none --linter=eslint --name=prisma-generated --unitTestRunner=none
This creates a new library at libs/prisma-generated that both our frontend and backend can use.
Setting Up Multiple Entry Points
Here's where it gets interesting. We want to create two separate entry points in our library:
Node entry point - For server-side usage (NestJS)
Types entry point - For client-side usage (Angular)
This separation allows us to:
Import the full Prisma client in our backend
Import only the lightweight types in our frontend
Maintain clean boundaries between client and server code
Let's see how to configure these entry points!
Configuring TypeScript Paths
First, we need to update our tsconfig.base.json to create convenient import paths for our two entry points. Add these paths to the paths section:
...
"paths": {
"@shared/prisma-generated/client": [
"libs/prisma-generated/src/lib/client.ts"
],
"@shared/prisma-generated/types": [
"libs/prisma-generated/src/lib/types.ts"
]
}
...
This configuration allows us to:
Import the Prisma client using
@shared/prisma-generated/client(for NestJS)Import only types using
@shared/prisma-generated/types(for Angular)
Creating Our Entry Point Files
Now let's create the two files that will serve as our entry points. In your libs/prisma-generated/src/ directory, create:
client.ts - For server-side usage:
// This file will export the Prisma client and related functionality
// We'll populate this after setting up Prisma
types.ts - For client-side usage:
// This file will export only types and enums
// We'll populate this with generated Prisma types
These placeholder files will be populated once we configure the new Prisma generator. The beauty of this setup is that your frontend will never accidentally import server-side code!
Setting Up Prisma
Now let's initialize Prisma in our project. We'll use PostgreSQL as our database provider and configure the output to generate files directly into our shared library.
First, navigate to your workspace root and initialize Prisma:
npx prisma init --datasource-provider postgresql --output ../libs/prisma-generated/src/lib/generated
This command does several important things:
Creates a
prisma/schema.prismafile with PostgreSQL configurationSets up a
.envfile with database connection variables (remember add it to our.gitignore)Configures the output directory to generate files in our shared library
The --output flag is crucial here because it tells Prisma to generate the client files directly into our libs/prisma-generated/src/lib/generated directory instead of the default node_modules location. This is exactly what we want for sharing types across our monorepo!
Configuring the New Generator
Here's where the magic happens! Prisma creates the schema file at prisma/schema.prisma. We need to make one important change to use the new generator.
Open prisma/schema.prisma and update the generator configuration. Change this:
generator client {
provider = "prisma-client-js"
output = "../libs/prisma-generated/src/lib/generated"
}
To this:
generator client {
provider = "prisma-client". // change from prisma-client-js to prisma-client
output = "../libs/prisma-generated/src/lib/generated"
}
Important: The new generator uses "prisma-client" instead of "prisma-client-js". This new generator is what gives us the tangible, shareable types that we can use across our entire monorepo. While prisma-client-js is still the default, prisma-client is the future and provides much better developer experience for monorepo setups.
Important: Adding Generated Files to .gitignore
Before we generate our files, we need to add the generated folder to our .gitignore. This is crucial because:
When developing locally or in CI,
npx prisma generatedetects your operating systemIt downloads the appropriate executable for your OS (Windows, macOS, Linux)
It regenerates all types and client files specific to your environment
Add this line to your .gitignore file:
# Prisma generated files
libs/prisma-generated/src/lib/generated/
This ensures that:
Generated files aren't committed to version control
Each developer gets the correct binaries for their OS
CI/CD pipelines generate fresh files for their environment
No conflicts between different operating systems
Generating Our Prisma Files
Now let's generate our Prisma client and types to see the magic in action:
npx prisma generate
This command will create all the necessary files in libs/prisma-generated/generated/ including types, enums, and the Prisma client that we can now share across our entire monorepo!
Exploring the Generated Files
After running npx prisma generate, you'll see a new file structure in your libs/prisma-generated/src/lib/generated/ folder:
libs/prisma-generated/
└── src/
└── lib/
└── generated/
├── internal/
├── models/
├── browser.ts
├── client.ts
├── commonInputTypes.ts
├── enums.ts
├── libquery_engine-darwin-arm64.dylib.node // OS-specific binary
└── models.ts
These generated files contain:
client.ts: The main Prisma client for server-side usage
models.ts: All your database model types
enums.ts: Database enums as TypeScript enums
browser.ts: Browser-compatible client (lighter version)
commonInputTypes.ts: Input types for queries and mutations
libquery_engine-*.node: OS-specific query engine binary
Now we can wire up our entry points to expose these generated types!
The Critical Problem with Frontend Builds
Here's where we encounter the most important detail of this entire setup. Prisma generates:
client.ts: The full client for server-side usage (NestJS)
browser.ts: A "browser-compatible" client for frontend usage
However, there's a catch! Even though browser.ts is meant for browser usage, it still contains Node.js dependencies that will break your Angular build process.
The Solution: Type-Only Exports
This is where the magic happens. We'll use TypeScript's type export to filter out everything that isn't a type, leaving us with clean, Node.js-free types for our frontend.
Update your libs/prisma-generated/src/lib/types.ts file:
// Export clean types for frontend (no Node.js dependencies)
export type * from './generated/browser';
export type * from './generated/enums';
The type keyword is crucial here because:
It exports only TypeScript types and interfaces
It filters out any actual JavaScript code or Node.js dependencies
It ensures your Angular build won't fail due to server-side imports
Your frontend gets all the type safety without any runtime baggage
This approach gives you the best of both worlds: full type safety in your frontend without breaking your build process!
Configuring the Client Entry Point
Now let's set up our server-side entry point. Update your libs/prisma-generated/src/lib/client.ts file:
// Export Prisma client for backend use
export { PrismaClient, Prisma } from './generated/client';
This entry point:
Exports the full
PrismaClientwith all its functionalityExports the
Prismanamespace with utilities and helpersIs intended only for server-side usage (NestJS)
Contains Node.js dependencies that would break frontend builds
Testing Our Setup with a Real Example
Let's create a practical example to see our shared types in action. Add this model and enum to your prisma/schema.prisma file:
// Enum for blog post status
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
// Simple table for blog posts
model Post {
id String @id @default(cuid())
title String
content String?
status PostStatus @default(DRAFT)
authorName String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("posts")
}
Now regenerate your Prisma client to create the types:
npx prisma generate
This will generate:
Post type - TypeScript interface for our blog post
PostStatus enum - TypeScript enum with DRAFT, PUBLISHED, ARCHIVED values
Input types - For creating and updating posts
Query helpers - For the backend Prisma client
Now we can use these types across our entire stack!
Using Types in Angular (Frontend)
Now let's see the magic in action! In your Angular application, you can import and use the generated types. Update your apps/blog-app/src/app/app.component.ts:
import { Component } from '@angular/core';
import { RouterModule } from '@angular/router';
import { NxWelcome } from './nx-welcome';
import { Post } from '@shared/prisma-generated/types';
@Component({
imports: [NxWelcome, RouterModule],
selector: 'app-root',
templateUrl: './app.html',
styleUrl: './app.scss',
})
export class App {
protected title = 'blog-app';
protected posts: Post[] = [];
}
Notice how we:
Import only the
Posttype using@shared/prisma-generated/typesGet full TypeScript intellisense for the Post interface
No Node.js dependencies are included in our Angular build
The type includes all properties:
id,title,content,status,authorName,createdAt,updatedAt
Your Angular build will work perfectly because we're only importing types, not any server-side code!
Using Types in NestJS (Backend)
The beauty of this setup is that we can also use the same types in our backend! In your NestJS application, you can import both the client and the types. For example, in your apps/blog-api/src/app/app.service.ts:
import { Injectable } from '@nestjs/common';
import { PrismaClient } from '@shared/prisma-generated/client'; // client
import { Post } from '@shared/prisma-generated/types'; // all the types
@Injectable()
export class AppService {
private prisma = new PrismaClient();
async getAllPosts(): Promise<Post[]> {
return this.prisma.post.findMany();
}
}
Notice how we:
Import
PrismaClientfrom@shared/prisma-generated/clientfor database operationsImport
Posttype from@shared/prisma-generated/typesfor type safetyBoth Angular and NestJS use the exact same
PosttypePerfect TypeScript intellisense in both frontend and backend
Enforcing Boundaries with Nx
Here's something super cool: we can use Nx's enforce-boundary rules to prevent improper imports and ensure our architecture stays clean. This prevents:
Angular accidentally importing the full Prisma client
Backend accidentally importing browser-specific code
Maintaining proper separation between client and server concerns
You can configure these rules in your .eslint.config.mjs to enforce that Angular can only import from @shared/prisma-generated/types and NestJS can only import from @shared/prisma-generated/client.
This architectural safety net ensures your team can't accidentally break the build or introduce unwanted dependencies!
What We Accomplished
In this guide, we've built a powerful full-stack setup that solves one of the biggest challenges in modern web development: maintaining type safety across your entire application. Here's what we achieved:
🏗️ Monorepo Setup
Created an Nx workspace with Angular and NestJS applications
Set up a shared library for Prisma-generated types
🔧 Prisma Configuration
Used the new
prisma-clientgenerator (instead ofprisma-client-js)Configured output to generate files in our shared library
Added generated files to
.gitignorefor proper CI/CD support
📦 Smart Entry Points
Created separate entry points for client (
@shared/prisma-generated/client) and types (@shared/prisma-generated/types)Used
export type *to filter out Node.js dependencies for frontend buildsEnsured Angular builds work without server-side code
🎯 Type Safety Everywhere
Shared the same
PostandPostStatustypes across frontend and backendMaintained full TypeScript intellisense in both applications
Eliminated type duplication and synchronization issues
🛡️ Architectural Safety
Leveraged Nx's enforce-boundary rules to prevent improper imports
Protected against accidental mixing of client and server code
Try It Yourself
Ready to see this in action? I've created a complete working example that demonstrates everything we covered in this post. You can clone the repository and explore the setup:
🔗 GitHub Repository: https://github.com/oidacra/shared-prisma-types-monorepo
The repository includes:
Complete Nx monorepo setup
Working Angular and NestJS applications
Shared Prisma types library
Example blog post model and service implementations
Ready-to-run development environment
Clone it, run npm install, set up your database connection, and see the power of shared Prisma types in action!
Verifying Everything Works
To confirm that our setup is working correctly and that both applications build without errors, you can run:
bash
npx nx run blog-api:build
npx nx run blog-app:build
Both builds should complete successfully with no errors! This proves that:
Angular builds cleanly without Node.js dependencies
NestJS has access to the full Prisma client
Our type separation strategy works perfectly
The shared types are properly configured
This approach will transform how you build full-stack applications, giving you the confidence that your frontend and backend are always in perfect sync. Happy coding! 🚀


