Making a GraphQL API with Prisma2 and Photon

simmetopia

Simon Bundgaard-Egeberg

Posted on August 2, 2019

Making a GraphQL API with Prisma2 and Photon

Prisma2 is a ORM/Database Migration tool which creates a GraphQL api from your server to your database.
This blog will cover some of the concepts of Prisma2, but it will not be a "getting started" tutorial, for this refer to Prisma's own documentation: Getting started

When creating a server with prisma2 and Photon we will need:

  1. To declare a database Schema in a prisma syntax.
  2. To create an app schema which will be exposed to the client.

Before we get into the graphql development, we need to understand what a GraphQL type and query is, because it is these we will implement on the server.

A graphql type

type User {
  id: ID!
  name: String
  purchases: [Transaction!]
  sells: [Transaction!]
  username: String!
}

type Query {
  user(userId: ID!): User
}

A query on the type

query MyNewUserQuery($id: ID!) {
  user(userId: $id) {
    name
    id
    username
  }
}

Learn more about GraphQL here

Prisma Schema

The Prisma schema is much like writing in
SDL, except for some minor syntax changes, like the : between the field and fieldtype.

model User {
  id        String        @default(cuid()) @id @unique
  username  String        @unique
  details   UserDetails
  purchases Transaction[] @relation(name: "TransactionToUser2")
  sells     Transaction[] @relation(name: "TransactionToUser")
}

model Transaction {
  id     String @default(cuid()) @id @unique
  item   Item
  price  Int
  seller User   @relation(name: "TransactionToUser")
  buyer  User   @relation(name: "TransactionToUser2")
}

A few things to notice here, the id of each model is annotated and a id, which means it will be indexed in the database. Prisma gives a set of default functions which, in this case, can create a default UID cuid().
In the models above it is possible for a user to have a set of purchases and a set of sells. This means that there will be two different fields that reference the same model (the same goes for the User in the Transaction model), we can however name the relation, which makes sure the the correct fields map to the correct users.

Since Prisma2 is creating the database model, it is almost trivial to make a many to many relation by simply annotating each to have a array of the opposite type:

model User {
  id           String         @default(cuid()) @id @unique
  transactions Transaction[] 

}

model Transaction {
  id     String @default(cuid()) @id @unique
  users  User[]
}

When the schema is written to the database, Prisma2 will generate a Photon API which can be instantiated and used to query the database.

If i was to return an array of all users i would be able to use photon like photon.users.findMany() which would return a list of all users.

In the findMany function you can give a restriction object to restrict the returned data, if i only wanted the users which username was 'Yes' (i don't know why though). ctx.photon.users.findMany({ where: { username: 'Yes' } }),
the general type generated in the argument for the findMany is:

export declare type FindManyUserArgs = {
    select?: UserSelect;
    include?: UserInclude;
    where?: UserWhereInput;
    orderBy?: UserOrderByInput;
    skip?: number;
    after?: string;
    before?: string;
    first?: number;
    last?: number;
};

select and include is used to blacklist/whitelist fields you want from the type.
Note that these types is generated to match the prisma schema, so all generated types might not match this type, but it paints a pretty picture of how well thought out this API is.

Pagination

As shown in above type you can do two different types of pagination, the "Take - Skip" and the "First - After" which is a cursor based pagination.

Prisma2 pushes the boundaries even further with @prisma/nexus which will from the generated types give you intellisense when creating your app schema. (This is the part where to select which data from the database you want to expose to the client)

// src/types/User.ts
import { objectType } from '@prisma/nexus';

export const User = objectType({
  name: 'User',
  definition(t) {
    t.model.id();
    t.field('computedField', {
      type: 'String',
      resolve: () => 'some computed value',
    });
    t.model.purchases({ type: 'Transaction' });
    t.model.sells({ type: 'Transaction' });
    t.model.username();
  },
});

The generated types from Prisma2 will add the schematypes to the objectType global if the name property matches the model name.
So t.model.purchases({type: 'Transaction'}) is typesafely inferred from the User model shown in the prisma schema above.
However if i wanted a computed field, i can just add that with no fuss.

the above code will generate a graphql API you can query like shown in the start of the blogpost.

Talk about full circle =)

💖 💪 🙅 🚩
simmetopia
Simon Bundgaard-Egeberg

Posted on August 2, 2019

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related