Putting together Prisma & Apollo Server 2
Starting off with Prisma can be super confusing in the beginning. So much talk of new system technology that talks to your database with Graphql. I recently went through all the late nights of documentation and prototypes to get my shiny new backend in production. There are a lot of ways to build api’s in Node.Js. I am sharing in this story what I learnt from setting up Prisma with Apollo and what worked for me. You can find the github directory at: https://github.com/Andre153/prisma-apollo
The image above is a representation of what we are putting together here and it flows from left to right.
- The first icon is a client making a request to our API.
- The second icon is our Apollo Graphql endpoint that is exposed to the client. This is where the request would be received.
- The third icon is our prisma setup. The Apollo graphql schema would start by passing the message along to the resolvers. Here within the resolvers the prisma client is put to use. The prisma client talks to the prisma server that exposes Graphql, and then finally interacts with the database.
Essentially you have a client talking to graphql, that talks to graphql, that talks to a database. This will make sense soon.
The first very important thing to understand is what prisma is exactly. Let me explain it to you as simple as I possibly can.
Prisma is a “ORM” server that runs on top of your database. That is it. When the server is started it compares your schema to the database and puts an api on top of it. You can think of it as a glorified ORM. Since this ORM is started as a server and exposes a Graphql schema for you to interact with your database.
Before we dive into more specifics around prisma let break it up into 4 sections:
- Prisma Files
- Prisma datamodel
- Prisma Client
- Prisma Server
Those are the 4 sections that I am going to cover in this part of the series. Prisma is basically just these sections. Once you understand how they work you will have a fun time building apps with prisma :)
Prisma Files
Now this may seem quite complicated at first. But its not. Prisma consists of:
- docker-compose.yml: this can be seen as your prisma server. This docker file contains what is required to start the server and get prisma running.
- prisma.yml: this is where you would define the relevant properties of prisma. You define your api secret , where your datamodel (database schema in graphql language) lies, the endpoint url of the server etc.
- datamodel.prisma file. This is the schema representation of your database in graphql language.
- .env file. This is just good practice to put properties like your api secret in. Also good practice to not commit this to your repository, but to create them locally or on your server along with the initial repository checkout.
These files can be created manually or by the use of the prisma-cli (which I do recommend installing) by running:
prisma init
You should be asked some questions regarding your database for this series we will create a new database. Your prisma files will now generate.
So there you go, that is 4 files that you need to manage. Not that bad now is it. So at the end of the day what you want to do is create a prisma directory put files number 2–4 in there to keep prisma operation files together and seperate from your project source directory.
Prisma datamodel
This file is straight forward. All you need to do here is define the schema of your database by the use of graphql language. The prisma documentation is really good with explaining the syntax. For this series our datamodel will have this schema:
type Investor {
id: ID! @unique
auth: AuthUser!
fistName: String!
lastName: String!
investments: [Investment!]
}
type AuthUser {
id: ID! @unique
token: String!
isActive: Boolean!
}
type Investment {
id: ID! @unique
header: String!
body: String!
}
this schema is an investor that has many investments with a one to one relationship with a table called AuthUser to keep track of any authentication attributes. Its good to think of this file as your entity/domain class if you compare it to traditional ORM frameworks.
There you have it. Prisma datamodel.
Prisma Client
The next very import part of understanding how prisma works is the prisma client. After you have created your prisma files and added you data model schema you can run:
prisma generate
This will generate the client the prisma client, and adds the directory to your project. Your project directory should look like this:
So what just happened? when you run prisma generate, it looks at your data model and creates a directory called prisma-client inside another directory called generated. This directory contains auto generated code that is used to interact with your database, you can see it as an auto generated ORM. I do not recommend altering this code since every time you alter your datamodel you will need to generate a new client. Rather alter your datamodel and generate a new client than altering the generated client. This would keep your project consistent and any future updates/migrations would be smooth.
After this client has been generated you can call the code to query or alter your data. It would form part of your business code in you handler/manager classes. Basically like interacting with a CRUD ORM like typeorm, sequelize etc etc.
There you have it. The prisma client.
Prisma Server
Oky soo this took ma a while to understand. But its not that hard to understand actually. Its a graphql server that takes the prisma requests made by the prisma client and translates it too SQL and then talks to your database. This server runs on the JVM and does query optimization too make sure that your queries are performing optimally. It’s important to understand this because the JVM normally requires a little bit more resources to boot up and run. I would recommend adding a little bit more resources to this server to get things going. You can vertically scale as required to find the sweet spot. I wouldn’t go less than 1gig ram.
The prisma server is defined in an docker file. For this series our docker file would look like:
version: '3'
services:
prisma:
image: prismagraphql/prisma:1.26
restart: always
ports:
- "4466:4466"
environment:
PRISMA_CONFIG: |
port: 4466
databases:
default:
connector: postgres
host: postgres
port: 5432
user: prisma
password: prisma
migrations: true
postgres:
image: postgres:10.5
restart: always
environment:
POSTGRES_USER: prisma
POSTGRES_PASSWORD: prisma
volumes:
- postgres:/var/lib/postgresql/data
volumes:
postgres:
Its basically spinning up prisma and postgres. You can define your database properties here.
Running this locally you would need Docker installed, and then a simple command within the directory of you docker file:
docker-compose up -d
Now you have prisma server and postgres running on your local. You can access the graphql client at http://localhost:4466 and interact with your database. Important thing to understand here is that you should probably not expose this as an API to your frontend. This should not be your public facing api and should be seen as a ORM on steroids running graphql. When your prisma server is running your prisma client can interact with your database.
The next thing to do now is to create our public facing grahpql api. This is where apollo server 2 comes in. This API would be exposed and as a request comes we will use the prisma client to interact with our database so that we can start writing some business code.
Setting up Apollo Server 2
The next thing to do in our project is to create a src directory. This directory would contain the apollo server schema and the resolvers. Before we continue lets take a look at the package.json to see all the required goodies.
"scripts": {
"start": "ts-node src/index.ts"
},
"dependencies": {
"@types/cors": "^2.8.4",
"@types/express": "^4.16.1",
"@types/graphql": "^14.0.5",
"apollo-server-express": "^2.4.6",
"graphql-import": "^0.7.1",
"prisma-client-lib": "^1.26.6"
},
"devDependencies": {
"graphql": "^14.1.1",
"graphqlgen": "^0.6.0-rc9",
"prisma": "^1.27.1",
"ts-node": "^8.0.2",
"tslint": "^5.13.0",
"typescript": "^3.3.3333"
}
- @types: I am a big fan of typescript. Therefore you will be required to add the type files.
- apollo-server-express: I am using this version of apollo since it has express going as well. This way you can make use of express middleware which has been proven to work really well. There are a lot of usecases I can think of that you can use it. But in this case the express middleware will execute before even hitting the graphql resolvers. Works well for auth.
- ts-node: running typescript for local development. For production I would recommend building a dist directory with tslint and running out of that.
- graphql-import: this is a really nice library for separating your schema into different files and bringing them together as one import for run time.
- graphqlgen: this is quite an important library since it speeds up your development time significantly. It generates your boilerplate resolvers by looking at your public schema and prisma client.
The first thing you have to do is create a src directory and a index.ts file. This file is where the server code will be.
const graphqlPath: string = '/api/graphql'
const schema = path.resolve('src/schema/Schema.graphql')
const typeDefs = importSchema(schema)
const app: express.Application = express();
app.use(cors())
const server = new ApolloServer({
typeDefs: typeDefs as any,
resolvers: resolvers as any,
introspection: true,
context: ({ req }): Context => {
return {
db: prisma,
userUUID: req.uuid
}
},
}) as any
server.applyMiddleware({
app,
path: graphqlPath
})
app.listen({ port: process.env.PORT }, () => {
console.log('Server rocking on PORT', process.env.PORT)
})
Once you have setup this file you can go ahead and create your schema directory for Apollo Server. Important thing to remember here is that this is the schema that will be client facing. You will define Queries, Mutations, Inputs, and Types. You can create a schema directory and put all in there. You directory should look something like this:
The Schema.graphql is the root file. You can import the rest of your files in there and in your index.ts file you import the schema like:
const schema = path.resolve('src/schema/Schema.graphql')
const typeDefs = importSchema(schema)
After you have defined and imported your schema its time to generate your boilerplate resolvers. For this to happen we are using the library graphqlgen and we need to define a graphqlgen.yml too give the library some knowledge on where to go and look for our schemas:
language: typescript
schema: ./src/schema/Schema.graphql
context: ./src/types/types.ts:Context
models:
files:
- ./prisma/generated/prisma-client
- ./src/types/types.ts
output: ./src/generated/graphqlgen.ts
resolver-scaffolding:
output: ./src/generated/tmp-resolvers/
layout: file-per-type
This file give the library context of our prisma client and the client facing schema we are trying to create. When you run graphqlgen you will find a new generated directory within your src directory. You can go ahead and create new directory called resolvers and within the generated directory copy all the resolvers into your directory. They are boilerplates and do nothing. All you need to do know is write the business code within those copied resolvers.
Things to note about Prisma
Its technically Scala running on the JVM. You will have to add some more resources than your traditional node services.
Prisma has like a 1000 task limit per request to protect the database. If you create a query that creates 1000 tasks you are going to get an error that would look something like this:
java.util.concurrent.RejectedExecutionException: Task slick.basic.BasicBackend$DatabaseDef$$xx$2@xx rejected from slick.util.AsyncExecutor$$xx$2$$xx$1@xx[Running, pool size = 9, active threads = 9, queued tasks = 1000, completed tasks = xxxx]
There are ways of dealing with this:
- Scaling horizontally. This would mean that you have a secondary prisma server running. You have implement a message queue first ( RabbitMQ being the recommended one at the time of this writing ) and disabling the management api.
- At the end of the day it all comes down to system design. Your client needs to have pagination etc that would prohibit behavior of sending requests that generate large amount of tasks.
Summary
Prisma is fun, innovative and new. Give it a try