Dimitri Borgers
Dimitri Borgers

Reputation: 278

Using Prisma Schema in PNPM monorepo with NestJS causing ESM issues

I've created a PNPM monorepo that has two NestJS apps (apps/api and apps/backend; both are exactly the same) and one Prisma database package (packages/database). When I run the api with start:dev, I get the following error:

(node:69498) Warning: To load an ES module, set "type": "module" in the package.json or use the .mjs extension.
(Use `node --trace-warnings ...` to show where the warning was created)
/Users/username/Documents/Slug/slug-monorepo/packages/database/src/index.ts:1

export * from "@prisma/client";
^^^^^^

SyntaxError: Unexpected token 'export'
    at wrapSafe (node:internal/modules/cjs/loader:1389:18)
    at Module._compile (node:internal/modules/cjs/loader:1425:20)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1564:10)
    at Module.load (node:internal/modules/cjs/loader:1287:32)
    at Function.Module._load (node:internal/modules/cjs/loader:1103:12)
    at Module.require (node:internal/modules/cjs/loader:1310:19)
    at require (node:internal/modules/helpers:179:18)
    at Object.<anonymous> (/Users/username/Documents/Slug/slug-monorepo/apps/api/src/prisma/prisma.service.ts:2:1)
    at Module._compile (node:internal/modules/cjs/loader:1480:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1564:10)

How can I use Prisma with NestJS in a PNPM monorepo?

./pnpm-workspace.yaml

packages:
  - 'apps/*'
  - 'packages/*'

apps/api/package.json

{
  "name": "@slug-monorepo/api",
  "version": "0.0.1",
  "description": "",
  "author": "",
  "private": true,
  "license": "UNLICENSED",
  "scripts": {
    "build": "nest build",
    "format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "start:debug": "nest start --debug --watch",
    "start:prod": "node dist/main",
    "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage",
    "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
    "test:e2e": "jest --config ./test/jest-e2e.json"
  },
  "dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.0.0",
    "@slug-monorepo/database": "workspace:*",
    "reflect-metadata": "^0.2.0",
    "rxjs": "^7.8.1"
  },
  "devDependencies": {
    ...
  },
  "jest": {
    "moduleFileExtensions": [
      "js",
      "json",
      "ts"
    ],
    "rootDir": "src",
    "testRegex": ".*\\.spec\\.ts$",
    "transform": {
      "^.+\\.(t|j)s$": "ts-jest"
    },
    "collectCoverageFrom": [
      "**/*.(t|j)s"
    ],
    "coverageDirectory": "../coverage",
    "testEnvironment": "node"
  }
}

apps/api/tsconfig.json

{
  "compilerOptions": {
    "module": "commonjs",
    "declaration": true,
    "removeComments": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "target": "ESNext",
    "sourceMap": true,
    "outDir": "./dist",
    "baseUrl": "./",
    "incremental": true,
    "skipLibCheck": true,
    "strictNullChecks": true,
    "noImplicitAny": true,
    "strictBindCallApply": true,
    "forceConsistentCasingInFileNames": true,
    "noFallthroughCasesInSwitch": true
  }
}

apps/api/src/prisma/prisma.service.ts

import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@slug/database';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}

apps/api/src/slug/dto/createSlug.dto.ts

import {
  SlugType
} from '@slug/database';

export class CreateSlugDto {
  @IsOptional()
  @IsEnum(SlugType)
  slug?: SlugType;
}

packages/database/package.json

{
  "name": "@slug-monorepo/database",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.ts",
  "scripts": {
    "build": "tsc"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "@types/node": "^20.14.8",
    "prisma": "^5.16.0",
    "typescript": "^5.1.3"
  },
  "dependencies": {
    "@prisma/client": "^5.16.0"
  }
}

packages/database/tsconfig.json

{
  "compilerOptions": {
    "target": "ESNext",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "outDir": "dist"
  },
  "include": ["./**/*.ts"],
  "exclude": ["dist", "node_modules"]
}

packages/database/src/index.ts

export * from "@prisma/client";

Upvotes: 3

Views: 726

Answers (1)

philolegein
philolegein

Reputation: 1525

I have no idea about the typescript stuff, so YMMV, but looking at those package.json files, I think I see a couple of things. First, your apps/api/package.json has neither a main nor a type. Second, in your packages/database/package.json, I don't think you can have a TypeScript file as the main entry point — I think you have to point it at the transpiled version that would come out in dist.

I found this quick article very useful. Here's my best guess at what you need:

 // File: apps/api/package.json
   "license": "UNLICENSED",
+  "type": "module",
+  "main": "dist/apps/api/index.js" // or wherever your transpile is
   "scripts": {
// File: packages/database/package.json
   "description": "",
-  "main": "src/index.ts",  // delete this
+  "exports": {
+    "types": "./dist/types.d.ts",
+    "require": "./dist/index.js",
+    "import": "./dist/index.mjs"
+   }, 
   "scripts": {

Hopefully that helps, or at least points you in the right direction.

Upvotes: 1

Related Questions