An introduction into Prisma

An introduction into Prisma

Prisma is a modern ORM replacement that turns a database into a fully functional GraphQL, REST or gRPC API. It provides powerful abstractions and building blocks to develop flexible and  scalable backends.

In this post, we will take a look at the most important concepts of Prisma and even build a little sample application to give you some practice using it. We will focus on GraphQL whenever we look at practical examples.

So, without wasting any further time, let’s get started.

Why care about Prisma

Now the question remains why you should use Prisma in the first place. Here are some reasons why developers should consider using Prisma and where it can be of help.

Typesafe:

Prisma provides a type-safe API that can be used from the front and backend including filters, aggregations, and transactions. This drastically increases the intelligent auto-completion or jump-to-definition features of IDEs and also makes code generation easier.

SDL (Schema definition language):

Prisma lets you define your models using their modern SDL and migrates your underlying database automatically.  It provides a concise and powerful way to specify your GraphQL schema. For more information refer to this article from the official Prisma blog.

Simple database workflows:

Prisma’s overall goals are to remove complexity from database workflows and simplify data access in your applications by providing:

  • Simple and powerful API for working with relational data
  • Visual data management using Prisma Admin
  • Automatic database migrations
  • Easy data import and export

Clean and layer architechture:

Prisma provides a clean architecture which respects synchronization, query optimization/performance and security. They do so by providing a build-in DAT (data access layer) which is used to abstract the complexity of the database access so that the developers can focus on their data instead of worrying about how to store and retrieve it from the database.

prisma-architecture
prisma-architecture

Datamodel

The datamodel defines the underlying database schema and thus is the foundation for all the auto-generated CRUD operations of your Prisma client.

The data model is written using a subset of the GraphQL SDL and stored in files with the .prisma file extension. These files need to be referred to in your prisma.yml file under the datamodel property.

datamodel: datamodel.prisma

Building blocks:

Now, let’s take a look at the building blocks of our datamodel:

  • Types — Types concist of multiple fields which have a type and typically represent entities fron your application domain (e.g. User, Task). Each type is mapped to a database table and has auto-generated CRUD operations.
  • Directives — Directives are used to provide additional information or add specific behavior to the types in our datamodel.
  • Relations — A relation describes the relationship between two types.

Example:

A simple example of a database.prisma file:

type Post {
 id: ID! @id
 published: Boolean! @default(value: false)
 title: String!
 content: String!
 author: User!
}

type User {
 id: ID! @id
 email: String! @unique
 password: String!
 posts: [Post!]!
}

This example contains a few important concepts of datamodels:

  • One-to-many relationship between the User and Post
  • All fields are required as indicated by the ! operator
  • The fields use annotations such as @id to make them readonly in the exposed Prisma API
  • The @unique directive ensures that there will never be two records with the same value
  • The two types are mapped to database tables

Here is a visual representation of how the types will be mapped to database tables:

prisma-data-modelling
prisma-data-modelling

For more information on the Prisma datamodel, I recommend visiting the official documentation.

Prisma client

The Prisma client is an auto-generated library which replaces typical ORMs in your API. It provides the connection between the client and the Prisma server which sits on top of the database.

prisma-client
prisma-client

It provides a seamless API which lets you work with your relational data, JOINs and transactions.

It can be generated using the prisma generate command provided by the Prisma API. We can also change the configurations by editing the prisma.yml file.

generate:
 - generator: typescript-client
   output: ../src/generated/prisma-client

In this example, we define that our client should be generated in typescript and in which folder the output should be saved.

Now that we know the basics of the client let’s look at how we can use it for basic data access and manipulation.

Defining data:

The Prisma client is auto-generated from the datamodel.prisma file and exposes CRUD functionality to all of its models.

For the following sections let’s consider that we have this datamodel:

type Post {
 id: ID! @id
 createdAt: DateTime! @createdAt
 updatedAt: DateTime! @updatedAt
 title: String!
 published: Boolean! @default(value: false)
 author: User
}

type User { 
 id: ID! @id
 name: String
 email: String! @unique
 posts: [Post!]!
}

Reading data:

Whenever you query a database record from the Prisma client you automatically get all the scalar fields of your record. That means that by default you only get fields which aren’t considered as relations (e.g. we only get the id, name and email when we query our User record)

Fetching all records:

const users: User[] = await prisma.users()

Here we are fetching all the users from our database and save them into an array.

Fetching a single record:

Fetching single records is also straight forward. We just need to include the where parameter with a unique field like the id in our example.

const post: Post = await prisma.post({ id: 'cjli6tknz005s0a23uf0lmlve' })

Select fields:

By default, we get all scalar fields of our record. If we only want to get specific fields though we need to write our own query fragment which can look something like this.

const fragment = `
fragment UserWithPosts on User {
  id
  email
  posts {
    id
    title
  }
}

const user = await prisma.users().$fragment(fragment)

Here we use the $fragment API to specify the fields we want to query. After that, we fetch the fields using our Prisma service.

Writing data:

Now that we know how to read data from our database using the Prisma client let’s move on to writing data to the database.

Creating Records:

When creating new records in the database we use the create() method which takes one input object with all the scalar fields of the record.

Each method call returns a promise for an object which contains all the scalar fields of the model that was created.

const post: Post = prisma.createPost({
  title: 'An Introduction into Prisma',
  author: {
    connect: { email: 'test@test.com' },
  },
})

Here we create a new post with test@test.com as our author.

Updating Records:

When updating an existing record we need to use the update() method which takes one input object with two fields:

  • where — This field is used to identify the record that is going to be updated. You can use any unique field to identify the record.
  • data — An object that contains the scalar fields that will be updated
const updatedUser: User = await prisma.updateUser({
  data: {
    name: 'Test',
  },
  where: {
    email: 'test@test.com',
  },
})

Deleting Records:

When deleting one record from the database we use the delete() method which takes an where object as an input.

const deletedUser: User = await prisma.deleteUser({
  email: 'test@test.com',
})

Realtime subscription:

Now that we know how to define and manipulate data let‘s take a look at how we can subscribe to database events and receive updates in realtime using the $subscribe property.

The $subscribe property is based on Websockets and is auto-generated for each model type in your datamodel.

Basic subscription:

Subscribe to the create and update events of for our User module.

const createdAndUpdatedUserIterator: UserAsyncIterator = await db.$subscribe
  .user({
    mutation_in: ['UPDATED', 'CREATED'],
  })
  .node()

Subscription with query:

Subscribe to events of a specific user using the node property.

const createdUserIterator = await db.$subscribe
  .user({
    mutation_in: ['CREATED', "UPDATED"],
    node: {
      email_contains: `test@test.com`,
    },
  })
  .node()

If you want more information about the Prisma client you can visit the official documentation.

Prisma server

The Prisma server is an infrastructure component that is connected to your database. It needs to be configured with a database connection and your user credentials before deployment.

prisma-server
prisma-server

The main responsibility of the Prisma server is to translate request made by the Prisma client into actual database queries.

Local setup:

The currently recommended way of setting up the Prisma Server is through Docker. This can be done by writing a simple docker-compose.yml file which contains both a config for the Prisma Server and one for our database. After that, you just need to start it using the docker-compose up command.

This can be done using different databases like MySQL, PostgresSQL or MongoDB. Here I show a sample config using MySQL as the database.

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:latest
    restart: always
    ports:
      - '4466:4466'
    environment:
      PRISMA_CONFIG: |
        managementApiSecret: some-secret
        port: 4466
        databases:
          default:
            connector: mysql
            host: mysql-db
            port: 3306
            user: root
            password: root
  mysql-db:
    image: mysql:latest
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
    volumes:
      - mysql:/var/lib/mysql
volumes:
  mysql: ~

Here is a simple docker-compose file which will create two containers:

  • prisma — Contains our Prisma server
  • mysql-db — Container running a local instance of the MySQL database

The Prisma container uses mysql as its database which it can refer to with its container name.

Demo Servers(Prisma cloud):

Another option is to use Prismas cloud service instead of hosting the database and server locally. These demo servers are completely free but you need a Prisma Cloud account.

These servers have a rate limit of 10 requests per 10 seconds (on average) and can thereby not be used in any production environment. Here are some use cases where it makes sense to use a demo server.

Use cases:

Potential use cases for these servers are:

  • Prototyping
  • Personal projects without a notable user base
  • Learning

We will see how to use a demo server in the CRUD app example below.

Prisma admin

Prisma admin is a way to visually interact with your data and database. It also you to modify existing and add new data to your database using a modern and simple GUI.

Structure:

The Prisma Admin interface consists of 4 main areas:

  • Center — Query and result area
  • Right —  Detail area
  • Left — Sidebar
prisma-admin-structure
prisma-admin-structure

Getting access to Prisma admin:

The Prisma admin page is available at the /_admin endpoint of your Prisma service.

For example:

  • https://eu1.prisma.sh/myuser/project/dev/_admin
  • http://localhost:4466/_admin

You can find the endpoint of your Prisma service in your prisma.yml file. Prisma admin can also be opened using the prisma admin command in your console.

If you want to try it you can go ahead and open the demo service provided by Prisma themselves.

Writing data:

Prisma admin provides a GUI that lets you easily create, update and delete data in your database.

Every database operation is a two-step process:

  1. Make changes in the Prisma UI
  2. Once you are done click the Save to Database button in the bottom-right corner

Creating a basic server

Now that we have a theoretical understanding of Prisma and know what it is used for let’s build a simple GraphQL server with it to get a better grasp of the concepts and structure we talked about above.

Setup:

Let’s get started by downloading the GraphQL CLI using npm (Node package manager) and setting up our project.

npm install -g prisma graphql-cli

After that we can create our project by typing:

graphql create primsa-crud

Then you will see a selection of project templates that looks something like this.

prisma-create-project
prisma-create-project

We will choose typescript-basics and continue on by pressing enter. This will create a sample project with one GraphQL type called post and full CRUD functionality.

The folder structure should look something like this.

folderstructure

Project structure:

Now that we have set up our project let’s investigate the generated files and their roles in the project:

/prisma:

  • prisma.yml — Root configuration file for the Prisma database API
  • seed.graphql — Contains mutations to seed the database with data
  • datamodel.graphql — Contains the data models written in SDL

/src:

  • index.ts — the entry point to your server which starts the GraphQL server and sets the configuration
  • schema.graphql — Defines the application schema and contains the GraphQL API for the client
  • generated/prisma.graphql — Defines the Prisma schema and all the CRUD functionality which is auto generated by Prisma and should not be edited manually

Datamodel and Schema:

The most important files of every Prisma application are the datamodel.prisma and schema.graphql because they define our data model an are thereby the foundation of the API.

Here is what the basic data model looks like:

type Post {
  id: ID! @unique
  isPublished: Boolean! @default(value: "false")
  title: String!
  content: String!
}

Now let’s add a second type to get a good feeling about using the SDL.

type User {
  id: ID! @id
  email: String! @unique
  password: String!
}

Here we just add another type with three fields. We also use the @unique declarator to make sure that every value is only used once in the database.

Now that we have updated the datamodel.prisma file let’s continue by adding the User type and mutations to our schema.graphql file.

type Query {
  feed: [Post!]!
  drafts: [Post!]!
  post(id: ID!): Post
}

type Mutation {
  createUser(email: String!, password: String!): User
  createDraft(title: String!, content: String): Post
  deletePost(id: ID!): Post
  publish(id: ID!): Post
}

type Post {
  id: ID!
  published: Boolean!
  title: String!
  content: String!
}

type User {
  id: ID!
  email: String!
  password: String!
}

Let’s walk through the code:

  • We define the user type using standard GraphQL syntax
  • Add the createUser() function to our mutations
  • Add a users query which will get all previously saved users

Query and Mutation implementation:

Now only the implementation of our resolver function remains. So let’s implement them in our index.ts file.

const resolvers = {
 Query: {
  ...,
  users(parent, args, context: Context) {
   return context.prisma.users()
  },
 },
 Mutation: {
  createUser(parent, { email, password }, context: Context) {
   return context.prisma.createUser({ email, password })
  },
  ...
 },
}

Here we create two resolver methods which both invoke a method generated by the Prisma client instance which is called prisma and attached to the context object.

Deployment and testing:

Now that we have finished our application we just need to deploy it to a demo server and test it using the GraphQL Playground. We can do so using the following command.

prisma deploy

If a dialog appears you need to choose Demo server and the location with the best ping. Then you need to register an account on Prisma cloud and connect it.

After that is done you can run your application using this command:

yarn start
or
npm run start

Your application should now be running and you should be able to visit the GraphQL playground on http://localhost:4000.

Now we can test our queries and mutations:

Create Post:

mutation {
  createDraft(
    title: "Prisma is a great and easy tool",
    content: "It really is."
  ) {
    id
    title
    content
  }
}

Create User:

mutation {
  createUser(
    email: "test123@test.com",
    password: "test123"
  ) {
    id
    email
    password
  }
}

Get all users:

query{
  users{
    id
    email
    password
  }
}

If you have any problems or questions feel free to leave them in the comments below. The whole code can also be found on my Github.

Conclusion

You made it all the way until the end! Hope that this article helped you understand the basics of Prisma and why it is so useful to us backend developers.

If you have found this useful, please consider recommending and sharing it with other fellow developers. If you have any questions or feedback, let me know in the comments down below.

Read these next: