Prisma is an Object Relation Mapping (ORM) solution for TS/JS, it is by far my favorite ORM with its ability to introspect into existing databases, intuitive schema models, type-safe API and amazing tooling. With Prisma 3, the library now supports NoSQL databases like MongoDB, making it even more enticing to use for projects where relational databases are not necessary.
While the integration is amazingly smooth when you are working with a MongoDB deployment with replica sets enabled, if you try to run prisma against a database without it you may experience errors that looks something like this:
PrismaClientUnknownRequestError2 [PrismaClientUnknownRequestError]:
Invalid `prisma.post.create()` invocation in /index.ts:9:21
6 await prisma.$connect()
7
8 // Create the first post
→ 9 await prisma.post.create(
Error in connector: Database error. error code: unknown, error message: Transactions are not supported by this deployment
at cb (/node_modules/@prisma/client/runtime/index.js:34804:17)
at processTicksAndRejections (internal/process/task_queues.js:97:5) {
clientVersion: '3.xx.0'
}
The Prisma team explains that a replica set is required for transactions in MongoDB and that to prevent partial writes nested queries require transactions to be enabled. A simple solution to get a MongoDB instance with replica set out of the box is to deploy a free MongoDB Atlas instance, but that might not be viable some times especially if you just want a offline capable development environment.
So this guide will provide a few strategies for running a local deployment of MongoDB on Docker that will work with Prisma.
Use an image with replica sets enabled
This is the simplest solution as there are quite a few images on DockerHub that has replica sets enabled by default, the one we are using here is published by Bitnami, tagged bitnami/mongodb
.
To use the image in Docker Compose, simply replace the mongo
in your compose file with bitnami/mongodb:latest
, and add a few environment variables needed for replica sets, for example:
version: '3.8'
services:
database:
image: 'bitnami/mongodb:latest'
environment:
- MONGODB_ADVERTISED_HOSTNAME=database
- MONGODB_REPLICA_SET_MODE=primary
- MONGODB_ROOT_USER=anyuser
- MONGODB_ROOT_PASSWORD=anypassword
- MONGODB_REPLICA_SET_KEY=replicasetkey123
ports:
- '27017:27017'
Note that the MONGODB_REPLICA_SET_MODE
, MONGODB_REPLICA_SET_KEY
are required to enable replica sets, while MONGODB_ADVERTISED_HOSTNAME
is strongly recommended. Here is an explaination of what each of the environment variable does:
MONGODB_REPLICA_SET_MODE
: select the mode to run the replica set as, possible values areprimary
,secondary
, and arbiter. For our case,primary
is recommended.MONGODB_REPLICA_SET_KEY
: the replica set key, should be longer than 5 characters.MONGODB_ADVERTISED_HOSTNAME
: the advertised hostname, this is not required but if you find there to be an issue without it, set the value to either127.0.0.1
,host.docker.internal
,localhost
, or the service name depending on your setup.
This solution has a few gotchas:
- The
latest
version of the image is not compatible with Apple Silicon devices, so if you know the service may need to run on such device, usebitnami/mongodb:4.4
instead. - The image does not have any arm optimized tags, so expect worse battery life performance if used on Apple Silicon.
Build your own image on top of mongo
If you don't want to leverage an image built by a 3rd party (albite a verified one), you can build your own replica set enabled docker image for your local development, this is a Dockerfile I found to work well, taken from Prisma's examples
FROM mongo:5
# we take over the default & start mongo in replica set mode in a background task
ENTRYPOINT mongod --port $MONGO_REPLICA_PORT --replSet rs0 --bind_ip 0.0.0.0 & MONGOD_PID=$!; \
# we prepare the replica set with a single node and prepare the root user config
INIT_REPL_CMD="rs.initiate({ _id: 'rs0', members: [{ _id: 0, host: '$MONGO_REPLICA_HOST:$MONGO_REPLICA_PORT' }] })"; \
INIT_USER_CMD="db.createUser({ user: '$MONGO_INITDB_ROOT_USERNAME', pwd: '$MONGO_INITDB_ROOT_PASSWORD', roles: [ 'root' ] })"; \
# we wait for the replica set to be ready and then submit the commands just above
until (mongo admin --port $MONGO_REPLICA_PORT --eval "$INIT_REPL_CMD && $INIT_USER_CMD"); do sleep 1; done; \
# we are done but we keep the container by waiting on signals from the mongo task
echo "REPLICA SET ONLINE"; wait $MONGOD_PID;
Then add the image and necessary environment variables to your compose yaml:
version: '3.8'
services:
database:
build: ./database-img
environment:
- MONGO_INITDB_ROOT_USERNAME=anyuser
- MONGO_INITDB_ROOT_PASSWORD=anypassword
- MONGO_INITDB_DATABASE=database
- MONGO_REPLICA_HOST=172.17.0.1
- MONGO_REPLICA_PORT=27017
ports:
- 27017:27017
For this image, all 5 environment variables are necessary as they are used for initializing the database and creating the root user. It is recommended that you place the Dockerfile
in a separate directory, they way I structured it is:
project
│ docker-compose.yml
└───database-img
│ │ Dockerfile
That's it! You can test the connection with your database by using prisma db pull
or prisma db push