Skip to content

Using multiple different databases in NestJS app #6813

@cookieMonsterDev

Description

@cookieMonsterDev

I recently gained experience setting up database connections for PostgreSQL and MongoDB in NestJS. Looking back at my solution, I think it might be a good idea to update the current guide on using multiple databases in a single app to reflect a similar approach. While the existing guide might be good enough for someone, this is what I would suggest:

1. Folders structure

Honestly, I don't see much value in creating a separate folder in the root of the repo for each database schema. Instead, I would suggest grouping all database-related code into nested folders inside the prisma folder:

   📂 prisma
     |
     | --- 📂 mongodb 
     |        | --- schema.prisma
     |
     | --- 📂 postgres
     |        | --- schema.prisma
     |        | --- 📂 migrations

2. Custom output path

This really gave me a headache. According to the docs, generated types can be stored in ./src. I haven't tried this approach in plain Node with Express, but I recommend avoiding it in NestJS, because for some reason, it makes the generated files unloadable by Node. That is why for myself I store generated staff in node_modules.

generator client {
  provider = "prisma-client-js"
  output   = "../../node_modules/@prisma/mongodb-client"
}

datasource db {
  provider = "mongodb"
  url      = env("MONGO_DATABASE_URL")
}
generator client {
  provider        = "prisma-client-js"
  previewFeatures = ["fullTextSearchPostgres"]
  output          = "../../node_modules/@prisma/postgres-client"
}

datasource db {
  provider = "postgresql"
  url      = env("POSTGRES_DATABASE_URL")
}

3. Initializing Prisma Clients in Nest

Here is really not much to say, so I will just leave this code example:

import { PrismaClient } from '@prisma/mongodb-client';
import { Injectable, OnModuleInit } from '@nestjs/common';

@Injectable()
export class MongodbDatabaseService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}
import { PrismaClient } from '@prisma/postgres-client';
import { Injectable, OnModuleInit } from '@nestjs/common';

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

4. Helper scripts for migrations and schemas generations

I'm neither fun of this example for helper scripts, though it is really rare occasion just imagine how this will look when you have 4 databases...

All these manipulations are better handled in a separate script file. It's also important to mention that different databases have different features—for MongoDB, for example, this means there are no migrate support.

  "scripts": {
    "build": "nest build",
    "start": "nest start",
    "start:dev": "nest start --watch",
    "postinstall": "ts-node ./scripts/prisma.ts generate",
    "databases:generate": "ts-node ./scripts/prisma.ts generate",
    "databases:migrate": "ts-node ./scripts/prisma.ts migrate",
    "databases:deploy": "ts-node ./scripts/prisma.ts deploy"
  },
import 'dotenv/config';
import { readdirSync } from 'fs';
import { execSync } from 'child_process';

const PRISMA_PATH = './prisma';
const COMMANDS_EXTENSIONS = [{ folderName: 'mongodb', commands: ['migrate', 'deploy'] }];

const generateSchemaParam = (folderName: string) => `--schema ./${PRISMA_PATH}/${folderName}/schema.prisma`;

const commands = {
  generate: (folderName) => `npx prisma generate ${generateSchemaParam(folderName)}`,
  migrate: (folderName) => `npx prisma migrate dev ${generateSchemaParam(folderName)}`,
  deploy: (folderName) => `npx prisma migrate deploy ${generateSchemaParam(folderName)}`,
};

const [, , actions] = process.argv;

if (!commands[actions]) {
  console.error('Invalid action. Available actions: generate, migrate, deploy, studio');
  process.exit(1);
}

readdirSync('./prisma').forEach((folderName, index) => {
  const isExeption = COMMANDS_EXTENSIONS.some(
    (exeption) => exeption.folderName === folderName && exeption.commands.includes(actions),
  );

  if (isExeption) return;

  const cmd = commands[actions](folderName, index);
  execSync(cmd, { stdio: 'inherit' });
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    type/docsDocumentation creation, updates or corrections

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions