Home Blog About
Achieving Type Safety Across Angular and NestJS with Prisma's New Generator

Achieving Type Safety Across Angular and NestJS with Prisma's New Generator

15 min read angular nestjs prisma typescript
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-app

  • NestJS 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:

  1. Node entry point - For server-side usage (NestJS)

  2. 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.prisma file with PostgreSQL configuration

  • Sets up a .env file 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 generate detects your operating system

  • It 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 PrismaClient with all its functionality

  • Exports the Prisma namespace with utilities and helpers

  • Is 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 Post type using @shared/prisma-generated/types

  • Get 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 PrismaClient from @shared/prisma-generated/client for database operations

  • Import Post type from @shared/prisma-generated/types for type safety

  • Both Angular and NestJS use the exact same Post type

  • Perfect 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-client generator (instead of prisma-client-js)

  • Configured output to generate files in our shared library

  • Added generated files to .gitignore for 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 builds

  • Ensured Angular builds work without server-side code

🎯 Type Safety Everywhere

  • Shared the same Post and PostStatus types across frontend and backend

  • Maintained 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! 🚀

Logo
Arcadio QuinteroSystems Engineer

Sharing practical knowledge on software architecture, Angular best practices, and the evolving landscape of web development.

© 2025 Arcadio Quintero. All rights reserved.

Built withAnalog&Angular