Releases & MaintenanceFeatures in Preview

MongoDB

Concepts

With the 1.22-release, Prisma introduced a database connector for MongoDB. MongoDB is a document database. Document databases stand in contrast to relational databases (RDBMS).

Here is an overview of how the vocabulary differs between the two database types:

RDBMSMongoDB
DatabaseDatabase
TableCollection
RowDocument
IndexIndex
JOINEmbedded Document or Reference

Source: MongoDB Documentation

The MongoDB database connector currently does not support ACID transactions. Learn more in this GitHub issue.

Tables vs Collections

Relational databases store their data in tables that are defined within the database schema. Data is normalized (flattened) and relations between tables are implemented using keys as references between data records. A table contains a number of rows which represent instances of the table.

MongoDB doesn't have the concept of tables. It is a schemaless database that stores its data in unstructured collections. A collection contains a number of documents. A document is the most basic unit of data in MongoDB and is stored in BSON format. From a developer perspective, a document can be though of as a JSON object.

While MongoDB is a schemaless database, using it with Prisma does effectively impose a schema on it (in the form of the Prisma datamodel). When writing data to MongoDB only through the Prisma API, Prisma guarantees that the data is consistent with the defined schema.

If you start writing to MongoDB directly, Prisma can not guarantee that the data in the database still adheres to the schema.

Relations

One of the biggest differences between document and relational databases is the way how relations between data types are handled.

While relational databases use database normalization to store flat data records that reference each other via keys, document databases are able to store an object physically co-located to a related object within the same collection. The latter is called embedding data (i.e. a document in a collection can have embedded sub-documents/arrays which live inside the same collection).

With MongoDB, relations can be expressed either by embedding data directly in a parent document or by using references. A good overview on the differences can be found in the MongoDB documentation. Prisma adopts the idea of embedded types when modeling data for an underlying MongoDB.

Read below to learn how relations in MongoDB can be modeled with Prisma.

Migrations

Learn everything you need to know about migrations with MongoDB here.

Data modeling

Embedded types

The MongoDB connector introduces the concept of embedded types. An embedded type ...

  • ... always is annotated with the @embedded directive.
  • ... always has (at least) one parent type.
  • ... always is stored directly within its parent type's collection in the underlying Mongo database (i.e. an embedded type never has its own collection).
  • ... can not have unique fields (i.e. fields annotated with the @unique directive).
  • ... can not have have a (back-)relation to its parent type (but it can have relations to other non-embedded types).
  • ... can not be queried directly using the Prisma API but only via nested operations through the parent type.
  • ... can not be created, updated or deleted directly using the Prisma API but only via nested operations through the parent type.

Here is an example of a datamodel where Coordinates is defined as an embedded type:

type City {
  id: ID! @id
  name: String!
  coordinates: Coordinates
}

type Coordinates @embedded {
  latitude: Float!
  longitude: Float!
}

Here is an example of the data that's stored in the underlying MongoDB database based on this datamodel:

With this setup, it is not possible to query any instances of Coordinates directly because Coordinates is an embedded type. Coordinates can only be queried via the City type. Similarly, you can not create, update or delete coordinates directly, instead you need to create, update or delete a City in order to perform such operation on an instance of Coordinates.

You can learn more about embedded types in the specification.

With MongoDB, you can model relations in two ways:

  • Using an embedded type as explained above
  • Using references which are called links in Prisma terminology

A link relation with the MongoDB connector works in the way that:

  • One side (A) of the relation stores the ID of the document on the other side (B), this is called an inlined link
  • The other side (B) of the relation has no reference at all to the document on the initial side (A)
  • Each side of the relation is represented by its own collection in the underlying MongoDB, i.e. a link relation always spans accross multiple collections.

You can denote the side of the relation that should store the ID using the link parameter of the @relation directive. In the following example, the User type stores the ID values of all the Post documents it's related to. A Post document however doesn't store any information about its author in the underlying Mongo database:

type User {
  id: ID! @id
  name: String!
  posts: [Post!]! @relation(link: INLINE)
}

type Post {
  id: ID! @id
  title: String!
  author: User!
}

Here is an example of the data that's stored in the underlying MongoDB database based on this datamodel:

While this approach enables querying for Post documents directly in the Prisma API (as opposed to embedded types which can only be queried through nested operations via their parent types), there are performance considerations when modeling relations this way.

Operations that go from a Post to a User via the author field will be more expensive. This is because the underlying Post objects don't know who their authors are and Prisma needs to filter all Users to find who the author of Post is.

Directives

The MongoDB connector introduces a number of new directives and slight changes in the way how the datamodel behaves.

If you are interested in this topic, you can follow the discussion around the specification.

@id

In previous versions of the datamodel, there was only one way to assign a globally unique ID to a model:

type User {
  id: ID! @id
}

If a field was defined like this in a previous version of the datamodel, Prisma would generate a globally unique ID and insert this ID as the value for the id field whenever a new record was being created.

The @id directive now gives more flexibility to developers since it can be applied to any field in your datamodel (at most once per model), here is an example where the ID is stored in a field called myID:

type User {
  myID: ID! @id
}

The @id directive always implies that the respective field is unique (without the need to explicitly specify the @unique directive). Note that for the MongoDB connector, the @id directive is required on every model that's not embedded.

Learn more in the specification.

@createdAt & @updatedAt

In previous versions of the datamodel, the system fields createdAt and updatedAt could be used to store when a record was created as well as when a record was las updated:

type User {
  id: ID! @id
  createdAt: DateTime!
  updatedAt: DateTime!
}

The new version of the datamodel removes the opinionation about how the fields need to be named. Instead, you can annonate any field of the DateTime! type with the @createdAt or @updatedAt directive to achieve the same functionality:

type User {
  id: ID! @id
  created_at: DateTime! @createdAt
  updated_at: DateTime! @updatedAt
}

@db

The @db(name: String!) directive lets you be more explicit about mapping types and fields in your datamodel to the underlying database, i.e. it lets you specify a different name for a model in the API than the name that's used for the collection of the underlying database. This only works for non-embedded types, since embedded types don't have a collection that could be named.

Previously, Prisma would simply name the underlying table/collection after the model. The name argument of the directive defines the name of the collection or field that should be used in the database.

type User @db(name: "user") {
  id: ID! @id
  name: String!
}

type Post @db(name: "post") {
  id: ID! @id
  stars: Int! @db(name: "starCount")
}

@embedded

The @embedded directive is used for embedded types. Find more info above.

Limitations & Notes

  • Any field that's annotated with the @id directive is mapped to the _id field in MongoDB.
  • The MongoDB connector does not support transactions. Learn more in this GitHub issue.

Get started with a new database

1. Setup

Install the latest version of the Prisma CLI using the instructions here. Then run the following command to initialize a new Prisma service:

prisma init hello-mongo
Copy

This launches the interactive wizard that helps you configure Prisma. To test the new MongoDB connector, you need to select the following answers for the first two questions:

  1. Create new database
  2. MongoDB

As a last step in the wizard, you need to select your preferred language for the Prisma client that's used to read and write data against the Mongo database, we're choosing the JavaScript Prisma Client for this guide.

The Prisma CLI now created a hello-mongo directory for you which contains your project setup. The Docker Compose file is used to launch a Prisma server that's backed by a MongoDB. Navigate into the project directory and run the following command to start the Prisma server:

docker-compose up -d
Copy

This might throw an error if you already have Prisma Docker containers running locally. In that case, you can reset your Docker containers.

You can verify that the Docker containers for Prisma and for your MongoDB have been started correctly by running docker ps. This should print something similar to this:

$ docker ps
CONTAINER ID        IMAGE                             COMMAND                  CREATED              STATUS              PORTS                      NAMES
5f3d8cc20699        mongo:3.6                         "docker-entrypoint.s…"   About a minute ago   Up About a minute   0.0.0.0:27017->27017/tcp   hello-mongo_mongo_1
fc261a149740        prismagraphql/prisma:1.31    "/bin/sh -c /app/sta…"   About a minute ago   Up About a minute   0.0.0.0:4466->4466/tcp     hello-mongo_prisma_1

Once you're sure the Prisma server is running, you can deploy the Prisma service:

prisma deploy
Copy

Note that Prisma creates a new MongoDB database for each combination of service name and stage. Consider the following examples:

  • Deploy to endpoint: http://localhost:4466/hello-mongo/dev

    • Service name: hello-mongo
    • Stage: dev
    • Result: Prisma creates a database called hello-mongo_dev
  • Deploy to endpoint: http://localhost:4466/hello-mongo/prod

    • Service name: hello-mongo
    • Stage: prod
    • Result: Prisma creates a database called hello-mongo_prod
  • Deploy to endpoint: http://localhost:4466 (which is equivalent to http://localhost:4466/default/default)

    • Service name: default
    • Stage: default
    • Result: Prisma creates a database called default_default

2. Use the Prisma client to read and write data

You can now play around with the generated Prisma client. Run the following commands to initialize a simple Node app:

npm init -y
npm install --save prisma-client-lib
touch index.js
Copy

Now add the following code to index.js:

const { prisma } = require('./generated/prisma-client')

// A `main` function so that we can use async/await
async function main() {
  // Create a new user called `Alice`
  const newUser = await prisma.createUser({ name: 'Alice' })
  console.log(`Created new user: ${newUser.name} (ID: ${newUser.id})`)

  // Read all users from the database and print them to the console
  const allUsers = await prisma.users()
  console.log(allUsers)
}

main().catch(e => console.error(e))
Copy

You can run this script using the following command:

node index.js
Copy

This first creates a new document in the User collection and then retrieves all the documents from the User collection via the Prisma API.

3. Update the datamodel

Feel free to adjust the datamodel and explore some of the new features, for example:

type User @db(name: "user") {
  id: ID! @id
  email: String @unique
  name: String!
  posts: [Post!]! @relation(link: INLINE)
}

type Post @db(name: "post") {
  id: ID! @id
  wasCreated: DateTime! @createdAt
  wasUpdated: DateTime! @updatedAt
  title: String!
  published: Boolean @default(value: false)
  author: User
  comments: [Comment!]!
}

type Comment @embedded {
  text: String!
  writtenBy: User!
}
Copy

Don't forget to update the Prisma API and regenerate your Prisma client after you changed datamodel.prisma:

prisma deploy
prisma generate
Copy

Get started with an existing database

MongoDB is a schemaless database, which means it is possible to insert data of various structures into it. MongoDB never complains because a piece of inserted data doesn't adhere to some predefined, expected format. This is different from relational databases where data that's inserted needs to adhere to the predefined database schema.

This changes when using MongoDB with Prisma. Prisma adds a "schema" (i.e. the Prisma datamodel) on top of MongoDB.

Throughout your use of MongoDB with Prisma, when you're writing to the underlying MongoDB ...

  • ... only through the Prisma API, it is guaranteed that the data that's stored in the database always adheres to the schema. This holds as long as you're only performing additive changes to your Prisma datamodel. Once you start changing existing types and/or fields in the datamodel, you are responsible to keep the underlying data in sync with the datamodel because Prisma will never make changes to your underlying data and only perform additive migrations.
  • ... directly (e.g. using the Mongo shell), it can not be guaranteed that the data that's stored in the database always adheres to the schema. In that case, you are responsible to ensure that the data that's written adheres to the schema.

Therefore, when starting out with an existing MongoDB, it is extremely important that the legacy data is consistent and that it can be modeled accurately as a Prisma datamodel. You can use the prisma introspect command to derive a datamodel from the data that's currently stored in your MongoDB.

1. Setup

When starting with an existing database, ensure you have the following information handy:

  • Your MongoDB connection string which includes:

    • The IP of the host machine that's running the MongoDB server mongod, e.g. localhost.
    • The port where mongod is running on the host machine.
    • The credentials for a MongoDB admin user.
    • The name of the MongoDB database where the admin user is stored. (Typically called admin).
    • Whether the MongoDB is configured to use SSL or not.
  • The name of the MongoDB database that you want to use with Prisma.

Create a new directory for your Prisma service and add a Docker Compose file to it:

mkdir hello-mongo
cd hello-mongo
touch docker-compose.yml
Copy

Then add the following contents into the docker-compose.yml:

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.31
    restart: always
    ports:
      - '4466:4466'
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: mongo
            uri: __MONGO_DB_CONNECTION_STRING__
            database: __MONGO_DB_DATABASE_NAME__
Copy

Replace both placeholders that are written in uppercase letters with the appropriate values, e.g.:

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.31
    restart: always
    ports:
      - '4466:4466'
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: mongo
            uri: mongodb://myAdminUser:abc123@host.docker.internal:27017/admin
            database: mydb

If you're running MongoDB on your local machine (without Docker but just by starting it manually using mongod), you need to set the host part of the connection string to host.docker.internal instead of localhost or 127.0.0.1.

Once you're done, launch the Prisma server with the following command:

docker-compose up -d
Copy

The Prisma server is now connected to your MongoDB and runs on http://localhost:4466.

Next, you need to create your Prisma service configuration. Run the following command:

prisma init --endpoint http://localhost:4466
Copy

This creates the prisma.yml and datamodel.prisma files that make up your service configuration.

Before you continue, add the following root property to your prisma.yml:

databaseType: document

2. Data modeling and ensuring data consistency

This step is very important when you're starting out with an existing MongoDB! The goal is to create a datamodel that represents the data that's currently stored in the collections of your MongoDB. Next to modeling the data properly, you need to ensure that all data in the database adheres to the datamodel that you specify - otherwise the Prisma API might throw errors at runtime.

Here are a few rules of thumb for modelling the data:

  • Every collection in your MongoDB database should be represented by its own model type in the Prisma datamodel.
  • When a document inside a collection has embedded documents, the structure of these embedded documents needs to be modeled as an an embedded type.
  • A relation between documents of two separate collections needs to be modelled as a link relation.
  • Every model type needs to have exactly one field of type ID! that's annotated with the @id directive. This field gets mapped to the MongoDB object ID called _id.

You can use prisma introspect to suggest a datamodel.

3. Deploy the Prisma datamodel and generate the Prisma client

Once you are ready with defining your datamodel and have ensured that all currently stored data in the database adheres to the specified datamodel, you can start deploying the Prisma API.

For that, you first need to adjust the contents of your prisma.yml:

datamodel: datamodel.prisma
endpoint: http://localhost:4466
databaseType: document

generate:
  - generator: javascript-client
    output: ./generated
Copy

Next, run the following two commands to deploy your Prisma API and generate the Prisma client:

prisma deploy
prisma generate
Copy

4. Use the Prisma client to read and write data

The API of the generated Prisma client depends on your datamodel. For this example, we're assuming that your datamodel has a type User that's defined as follows:

type User @db(name: "users") {
  id: ID! @id
  name: String!
}

The @db directive expresses that the underlying collection inside your MongoDB is called users.

Run the following commands to initialize a simple Node app:

npm init -y
npm install --save prisma-client-lib
touch index.js
Copy

Now add the following code to index.js:

const { prisma } = require('./generated/prisma-client')

// A `main` function so that we can use async/await
async function main() {
  // Create a new user called `Alice`
  const newUser = await prisma.createUser({ name: 'Alice' })
  console.log(`Created new user: ${newUser.name} (ID: ${newUser.id})`)

  // Read all users from the database and print them to the console
  const allUsers = await prisma.users()
  console.log(allUsers)
}

main().catch(e => console.error(e))
Copy

You can run this script using the following command:

node index.js
Copy

This first creates a new document in the users collection and then retrieves all the documents from the users collection via the Prisma API.

Using the Mongo shell

You can always connect to your MongoDB server using the Mongo shell. Be careful with performing write-operations through the Mongo shell when using MongoDB with Prisma since you're loosing the guarantee that the stored data continues to adhere to the schema of the defined Prisma datamodel. It it recommended to only perform write-operations through the Prisma API to ensure data integrity.

When using MongoDB with Prisma following the setup in the "Get started with a new database"-section, you can use the Mongo shell to connect to the MongoDB server that's running with Docker. The following steps explain how to do so.

1. Installation

If you haven't done so before, you need to install MongoDB on your machine.

2. Launching the Mongo shell

Once it's installed, you can use the mongo command in your terminal to launch the shell.

3. Authenticating as an admin user

In the Docker Compose file that was created by the prisma init command, Prisma configured an admin user with the following credentials:

  • Username: prisma
  • Password: prisma

To use the admin user, you first need to switch to the admin database that MongoDB creates by default. You can do so using the following command:

use admin
Copy

Now you can authenticate using the credentials that were set through the Docker Compose file:

db.auth('prisma', 'prisma')
Copy

You can verify that the authentication was successful by running show dbs which will print a list of the available databases, looking similar to this:

> show dbs
admin            0.000GB
config           0.000GB
default_default  0.000GB
local            0.000GB
prisma           0.000GB

4. Exploring the MongoDB of a Prisma service

Prisma creates a database that is named after the name and the stage of your Prisma service. These are the two path components at the end of your service's endpoint.

Here are two examples:

  • A service with the endpoint http://localhost:4466 (which is equivalent to http://localhost:4466/default/default) creates a database called default_default.
  • A service with the endpoint http://localhost:4466/hello-mongo/dev creates a database called hello-mongo_dev

Switch to the database of your Prisma service, e.g. using this command:

use default_default
Copy

Now you can list all available collections with this command:

show collections
Copy

If you have a users collection available, you can run the following command to retrieve a single document from it:

db.users.findOne()
Copy

Learn more about the Mongo shell API here.

PRISMA_CONFIG

PRISMA_CONFIG is the environment variable that needs to be provided to the Prisma Docker image specifying to which database the Prisma server should connect.

Here is the reference with some example values for what PRISMA_CONFIG looks like for MongoDB:

PRISMA_CONFIG: |
  port: 4466
  databases:
    default:
      connector: mongo
      uri: mongodb://myAdminUser:abc123@host.docker.internal:27017/admin
      database: mydb

Here is an overview of the available properties:

  • connector: Specifies which database connector the Prisma server should use. Use mongo for MongoDB.
  • uri: Your MongoDB connection string, i.e. the URL that can connect to your MongoDB server. Here's an overview of the information it needs to contain:

    • The connection protocol which specifies whether you want to use the standard connection format (mongodb) or the DNS Seedlist Connection Format (mongodb+srv).
    • The credentials for a MongoDB admin user in the format username@password.

      • When starting with a new database, Prisma will create this user in the admin database.
      • When starting with an existing database, Prisma looks up the user with these credentials in the authSource database (see next property).
    • The IP/hostname of the host machine that's running the MongoDB server mongod, e.g. localhost (or host.docker.internal if you're running MongDB via Docker).
    • The port where mongod is running on the host machine. (Default: 27017).
    • The "authentication source", i.e. MongoDB database where the admin user is stored. (Default: admin).
    • A boolean that specifies whether the connection to the MongoDB should use SSL. (Default depends on the connection protocol. Default for mongodb+rsv: true. Default for mongodb: false).
  • database: The MongoDB database you want to connect to. This property is only required if you want to connect to an existing database, otherwise it can be omitted.
  • rawAccess: Specifies whether the executeRaw mutation will be enabled in the Prisma API. The MongoDB connector currently doesn't support raw access, so you need to set this to false or omit it.