DEV Community

Nathan Cook
Nathan Cook

Posted on • Edited on

Optimize Yarn 1.2 Monorepo Docker Development with Turborepo: Tips for Prisma and Dockerfile build filtering

Github Repository for this article

This yarn 1.2 monorepo demonstrates

  • Why putting Prisma in its own workspace is a good idea
  • Docker container build time difference between turbo run build --filter=service and turbo run build --filter=service^...

Setup

bash setup.sh
Enter fullscreen mode Exit fullscreen mode
#!/bin/bash

docker pull node:20.2.0-alpine3.17
docker pull postgres:15.3-alpine3.17

docker image build -f dockerfiles/Dockerfile.node -t custom-node:latest dockerfiles

yarn
yarn workspace prisma-client build
yarn
Enter fullscreen mode Exit fullscreen mode

This monorepo has the following workspaces:

  • apps/frontend
  • apps/backend
  • packages/prisma-client

Why put Prisma in a workspace?

App workspaces can access Prisma types

apps/backend/src/app.service.ts

import {Injectable} from "@nestjs/common";
import {PrismaService} from "nestjs-prisma";
import type {User} from "prisma-client";

@Injectable()
export class AppService {
  constructor(private readonly prisma: PrismaService) {}
  getHello(): string {
    return "Hello World!";
  }

  getUsers(): Promise<User[]> {
    return this.prisma.user.findMany();
  }
}
Enter fullscreen mode Exit fullscreen mode

apps/frontend/src/use-data.ts

import React from "react";
import {ofetch} from "ofetch";
import type {User} from "prisma-client";

export default function useData() {
  const [data, setData] = React.useState<User[]>([]);

  React.useEffect(() => {
    (async () => {
      const data = await ofetch("http://localhost:3333/users");
      setData(data);
    })();
  }, []);

  return data;
}
Enter fullscreen mode Exit fullscreen mode

In frontend/backend package.json

{
  "dependencies": {
    "prisma-client": "*"
  }
}
Enter fullscreen mode Exit fullscreen mode

Simplifies generating the prisma client in Docker containers

packages/prisma-client/package.json

{
  "name": "prisma-client",
  "version": "1.0.0",
  "main": "dist/index.js",
  "files": ["src/index.ts"],
  "repository": "https://github.com/moofoo/turborepo-tricks-prisma-workspace.git",
  "author": "moofoo <nathancookdev@gmail.com>",
  "license": "MIT",
  "private": true,
  "scripts": {
    "build": "npx -y rimraf dist/* && prisma generate && npx tsc"
  },
  "dependencies": {
    "@prisma/client": "^4.15.0"
  },
  "devDependencies": {
    "@types/node": "^20.2.5",
    "prisma": "^4.15.0",
    "rimraf": "^5.0.1",
    "typescript": "^5.1.3"
  }
}
Enter fullscreen mode Exit fullscreen mode

With prisma generate in the build script, turbo run build ... will generate the prisma client for workspaces that have prisma-client as a dependency. This is particularly useful when it comes to writing Dockerfiles, since it makes it easier to write generic Dockerfiles for development.

Build time difference between --filter=service and --filter=service^...

If you compare docker-compose.good.yml and docker-compose.bad.yml, you'll see that the only difference is whether the Node.js services use Dockerfile.good or Dockerfile.bad.

The only difference between those Dockerfiles is on line 30

Dockerfile.good

RUN turbo run build --no-cache --filter=${APP}^...
Enter fullscreen mode Exit fullscreen mode

Dockerfile.bad

RUN turbo run build --no-cache --filter=${APP}
Enter fullscreen mode Exit fullscreen mode

The latter RUN command is equivalent to running yarn workspace ... build for the given app workspace and all dependent workspaces

PLEASE NOTE THAT THE compare.sh BASH SCRIPT RUNS docker system prune -f TO ACCURATELY COMPARE BUILD TIMES

Run compare.sh to see the difference:

$ bash compare.sh
Cleaning up
Building bad
yarn run v1.22.19
$ docker compose -f docker-compose.bad.yml build --no-cache -q
Done in 155.18s.

real    2m35.339s
user    0m0.531s
sys     0m0.230s
Cleaning up
Building good
yarn run v1.22.19
$ docker compose -f docker-compose.good.yml build --no-cache -q
Done in 94.58s.

real    1m34.768s
user    0m0.498s
sys     0m0.165s
Enter fullscreen mode Exit fullscreen mode

--filter=workspace : 2m35.339s

--filter=workspace^... : 1m34.768s

(I intentionally didn't follow dependency best practices with the Frontend app to increase its build time, to make the difference between the two build commands more clear. The basic point is don't build workspaces if you don't need to)

Top comments (0)