Prisma Tutorial in Go

October 30, 2019

Recently I started to look at Prisma as an ORM for our graphql server. I looked at their tutorial for golang and I found going through it there were some errors. Some of the errors were simple code issues in the code, that were easily resolvable. But some of the errors, specifically in the resolver class just did not work; due to some of the resolver implementations being slightly incorrect. I am not a go expert but was able to fix it and finish the tutorial. Sad part was it took a lot longer than anticipated.

Also, instead of using “deps” to manage my dependencies, I wanted to use mod. So a lot of this will be a duplicate of the Prisma Golang Tutorial, but with what I believe are corrections and the usage of mod vs dep.

Thanks for reading!

Setting up Prisma and PostgreSQL on Docker

First you need to install Prisma

# on a mac 
brew tap prisma/prisma
brew install prisma

Now you need to install Docker

Create a new directory to house this tutorial. You can call it hello-world-prisma if you like. Once you create it, cd into it.

touch docker-compose.yml

Add the following in the docker-compose.yml, in this example I am going to use PostgreSQL

version: '3'
services:
  prisma:
    image: prismagraphql/prisma:1.34
    restart: always
    ports:
      - '4466:4466'
    environment:
      PRISMA_CONFIG: |
        port: 4466
        databases:
          default:
            connector: postgres
            host: postgres
            port: 5432
            user: prisma
            password: prisma
  postgres:
    image: postgres:10.3
    restart: always
    environment:
      POSTGRES_USER: prisma
      POSTGRES_PASSWORD: prisma
    volumes:
      - postgres:/var/lib/postgresql/data
volumes:
  postgres: ~

Save the file and close it. Now we are going to stand up our prisma server and database.

docker-compose up -d

Bootstrap the configuration files for your prisma client. The endpoint needs to match the URL of a running Prisma server.

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

You should see the following:

Created 2 new files:

  prisma.yml           Prisma service definition
  datamodel.prisma    GraphQL SDL-based datamodel (foundation for database)

Next steps:

  1. Deploy your Prisma service: prisma deploy

Now we need to deploy our prisma server

prisma deploy

You should see the following, notice it created a default User table with a couple of columns.

Creating stage default for service default 
Deploying service `default` to stage `default` to server `local` 1.0s

Changes:

  User (Type)
  + Created type `User`
  + Created field `id` of type `ID!`
  + Created field `name` of type `String!`

Applying changes 1.2s

Your Prisma endpoint is live:

  HTTP:  http://localhost:4466
  WS:    ws://localhost:4466

You can view & edit your data here:

  Prisma Admin: http://localhost:4466/_admin

Congratulations, you have successfully set up Prisma. You can now start using the Prisma client to talk to your database from code.

If you want to view and edit the data in your database, you can use Prisma Admin. To access Prisma Admin, you need to append /_admin to your Prisma endpoint, for example: http://localhost:4466/_admin.

Generate your Prisma Client

edit the prisma.yml file add the following to the end of the file:

generate:
 - generator: go-client
   output: ./generated/prisma-client/

Now generate the client

prisma generate

You should see the following:

Generating schema 13ms
Saving Prisma Client (Go) at /Users/chris/git/development/go_modules/graphql/hello-world-prisma/generated/prisma-client/

The CLI now stored your Prisma client inside the ./generated/prisma-client/ directory as specified in prisma.yml.

Prepare the Go client I am using go v1.13

First since we are using mod, let set that up first. Make sure you are in your hello-world-prisma directory. Type the following:

go mod init github.com/prismaTutorial

You should see the following:

go: creating new go.mod: module github.com/prismaTutorial

create a file called index.go, open up your favorite editor and add the following:

Note: I had to change the import statememt for the prisma import. The original import looked like this:

prisma "hello-world/generated/prisma-client"

As you can see below my import is slightly different. This is due to the mod init command I used, but I think if you used the dep init command you would have run into a similar issue and would not be able to use the code provided verbatim.

package main

import (
    "context"
    "fmt"
    prisma "github.com/prismaTutorial/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user
    name := "Alice"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name: name,
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    users, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", users)
}

We need to import dependencies. So type the following command:

go mod tidy

Let’s run the code

go run index.go

You should see the following:

Created new user: &{ID:ck2co6u6p000m0718s8l7vbv9 Name:Alice}
[{ID:ck2co6u6p000m0718s8l7vbv9 Name:Alice}]

Also if you go to http://localhost:4466/_admin you will see the new user in the table.

Change Datamodel

Update the datamodel, edit the datamodel.prisma file and update it with the following (overwrite what was in there). You will notice we added some more columns to User and add a new type to the model, Post.

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

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

Now add the following to your prisma.yml file. This will ensure when you deploy your new/updated prisma model your “generated/prisma-client” will be updated, otherwise if you do not add this you will manually have to generate the client again by typing “prisma generate”

hooks:
  post-deploy:
    - prisma generate

run the following command:

prisma deploy

You should see the following. Wait what is that error? Apparently you don’t need the post-hook anymore, so just ignore everything I said earlier related to the post-hook and what happens if you don’t add it. The tutorial is not up to date. But a caveat here, if you do not add the post hook. When I first did this tutorial, I did not see this notice/erro, so it is best you check your tables and generated client to see if the new items are added. Otherwise you will need to run “prisma generate”.

Deploying service `default` to stage `default` to server `local` 757ms

Changes:

  Post (Type)
  + Created type `Post`
  + Created field `id` of type `ID!`
  + Created field `title` of type `String!`
  + Created field `published` of type `Boolean!`
  + Created field `author` of type `User`

  User (Type)
  + Created field `email` of type `String`
  + Created field `posts` of type `[Post!]!`

  PostToUser (Relation)
  + Created an inline relation between `Post` and `User` in the column `author` of table `Post`

Applying changes 1.1s

post-deploy:
internal/modules/cjs/loader.js:594
    throw err;
    ^

Error: Cannot find module '/Users/chris/git/development/go_modules/graphql/hello-world-prisma/gener
    at Function.Module._resolveFilename (internal/modules/cjs/loader.js:592:15)
    at Function.Module._resolveFilename (pkg/prelude/bootstrap.js:1285:46)
    at Function.Module._load (internal/modules/cjs/loader.js:518:25)
    at Function.Module.runMain (pkg/prelude/bootstrap.js:1314:12)
    at startup (internal/bootstrap/node.js:274:19)
    at bootstrapNodeJSCore (internal/bootstrap/node.js:608:3)

Running prisma generate ✖
Warning: The `prisma generate` command was executed twice. Since Prisma 1.31, the Prisma client is
st-deploy` hook any more, you can therefore remove the hook if you do not need it otherwise.
Generating schema 20ms
Saving Prisma Client (Go) at /Users/chris/git/development/go_modules/graphql/hello-world-prisma/gen

Your Prisma endpoint is live:

  HTTP:  http://localhost:4466
  WS:    ws://localhost:4466

You can view & edit your data here:

  Prisma Admin: http://localhost:4466/_admin

Reading and Writing Nested Objects

Now I saved off the original index.go in github to index.go_1 for reference sake. I created a new index.go file and added the following code:

package main

import (
    "context"
    "fmt"

    prisma "github.com/prismaTutorial/generated/prisma-client"
)

func main() {
    client := prisma.New(nil)
    ctx := context.TODO()

    // Create a new user with two posts
    name := "Bob"
    email := "bob@prisma.io"
    title1 := "Join us for GraphQL Conf in 2019"
    title2 := "Subscribe to GraphQL Weekly for GraphQL news"
    newUser, err := client.CreateUser(prisma.UserCreateInput{
        Name:  name,
        Email: &email,
        Posts: &prisma.PostCreateManyWithoutAuthorInput{
            Create: []prisma.PostCreateWithoutAuthorInput{
                prisma.PostCreateWithoutAuthorInput{
                    Title: title1,
                },
                prisma.PostCreateWithoutAuthorInput{
                    Title: title2,
                },
            },
        },
    }).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Created new user: %+v\n", newUser)

    allUsers, err := client.Users(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allUsers)

    allPosts, err := client.Posts(nil).Exec(ctx)
    if err != nil {
        panic(err)
    }
    fmt.Printf("%+v\n", allPosts)
}

I run the new index.go

go run index.go

You should see the following, or something similar:

Created new user: &{ID:ck2e14hkl001k0a18e84ni0rz Email:0xc0000c1300 Name:Bob}
[{ID:ck2co6u6p000m0718s8l7vbv9 Email:<nil> Name:Bob} {ID:ck2e14hkl001k0a18e84ni0rz Email:0xc00018a2c0 Name:Bob}]
[{ID:ck2e14hkx001l0a18ms8dtyyr Title:Join us for GraphQL Conf in 2019 Published:false} {ID:ck2e14hl1001m0a18ggqjx372 Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

Now let’s query that data, if you copied the code from the prisma tutorial, it won’t work. The email is not correct, we did not insert a post for “Alice” we inserted a post for “bob”. So just add the following

// Query data - use the client and context defined from the above code, In my file I just commented out the post creation but reused the client and context in the index.go file.
  email := "bob@prisma.io"
  postsByUser, err := client.User(prisma.UserWhereUniqueInput{
    Email: &email,
  }).Posts(nil).Exec(ctx)

  if err != nil {
    panic(err)
  }
  fmt.Printf("%+v\n", postsByUser)

Run the code, you should see similar output:

[{ID:ck2e14hkx001l0a18ms8dtyyr Title:Join us for GraphQL Conf in 2019 Published:false} {ID:ck2e14hl1001m0a18ggqjx372 Title:Subscribe to GraphQL Weekly for GraphQL news Published:false}]

Build an App

We are going to use [gqlgen][gqlgen_url] as our graphql server.

In your project directory create a “scripts” directory and in that directory create a file named “gqlgen.go”, place the following code in the golang file:

If you are curious about the build ignore comment here is a link to the documentation

// +build ignore

package main

import "github.com/99designs/gqlgen/cmd"

func main() {
    cmd.Execute()
}

now make sure you have the dependencies required by running, check go.mod and go.sum and see if https://github.com/99designs/gqlgen is imported, I sometimes don’t see it when I run tidy, or need to run it multiple times, which it keeps adding and removing it, especially when you run it with the “-v” flag; weird

go mod tidy

Create a directory named “server” and in that directory create a file named “schema.graphql” add the following type definitions to it:

type Query {
  publishedPosts: [Post!]!
  post(postId: ID!): Post
  postsByUser(userId: ID!): [Post!]!
}

type Mutation {
  createUser(name: String!): User
  createDraft(title: String!, userId: ID!): Post
  publish(postId: ID!): Post
}

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

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

Configure gqlgen

In “servers” parent directory create a file named “gqlgen.yml” add the following:

schema: server/schema.graphql
exec:
  filename: server/generated.go
models:
  Post:
    model: github.com/prismaTutorial/generated/prisma-client.Post
  User:
    model: githbu.com/prismaTutorial/generated/prisma-client.User
resolver:
  # Goal: copy&paste from generated file
  filename: tmp/resolver.go
  type: Resolver

Use the gqlgen code generation script to create the foundation for your GraphQL server:

go run scripts/gqlgen.go

Running the above command generates a file, you want to copy that file from tmp to server

cp ./tmp/resolver.go ./server/

Now you need to implement the resolver.go functions. The original file looks like this:

package tmp

import (
  "context"

  prisma "github.com/prismaTutorial/generated/prisma-client"
  "github.com/prismaTutorial/server"
)

// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.

type Resolver struct{}

func (r *Resolver) Mutation() server.MutationResolver {
  return &mutationResolver{r}
}
func (r *Resolver) Post() server.PostResolver {
  return &postResolver{r}
}
func (r *Resolver) Query() server.QueryResolver {
  return &queryResolver{r}
}
func (r *Resolver) User() server.UserResolver {
  return &userResolver{r}
}

type mutationResolver struct{ *Resolver }

func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*prisma.User, error) {
  panic("not implemented")
}
func (r *mutationResolver) CreateDraft(ctx context.Context, title string, userID string) (*prisma.Post, error) {
  panic("not implemented")
}
func (r *mutationResolver) Publish(ctx context.Context, postID string) (*prisma.Post, error) {
  panic("not implemented")
}

type postResolver struct{ *Resolver }

func (r *postResolver) Author(ctx context.Context, obj *prisma.Post) (*prisma.User, error) {
  panic("not implemented")
}

type queryResolver struct{ *Resolver }

func (r *queryResolver) PublishedPosts(ctx context.Context) ([]*prisma.Post, error) {
  panic("not implemented")
}
func (r *queryResolver) Post(ctx context.Context, postID string) (*prisma.Post, error) {
  panic("not implemented")
}
func (r *queryResolver) PostsByUser(ctx context.Context, userID string) ([]*prisma.Post, error) {
  panic("not implemented")
}

type userResolver struct{ *Resolver }

func (r *userResolver) Posts(ctx context.Context, obj *prisma.User) ([]*prisma.Post, error) {
  panic("not implemented")
}

Replace the above with this:

package main

import (
  "context"

    prisma "github.com/prismaTutorial/generated/prisma-client"
    
    )

type Resolver struct {
  Prisma *prisma.Client
  
}

func (r *Resolver) Mutation() MutationResolver {
  return &mutationResolver{r}

  
}
func (r *Resolver) Post() PostResolver {
  return &postResolver{r}

  
}
func (r *Resolver) Query() QueryResolver {
  return &queryResolver{r}

  
}
func (r *Resolver) User() UserResolver {
  return &userResolver{r}

  
}

type mutationResolver struct{ *Resolver  }

func (r *mutationResolver) CreateUser(ctx context.Context, name string) (*prisma.User, error) {
  return r.Prisma.CreateUser(prisma.UserCreateInput{
      Name: name,
        
      }).Exec(ctx)

      
}
func (r *mutationResolver) CreateDraft(ctx context.Context, title string, userId string) (*prisma.Post, error) {
  return r.Prisma.CreatePost(prisma.PostCreateInput{
      Title: title,
      Author: &prisma.UserCreateOneWithoutPostsInput{
            Connect: &prisma.UserWhereUniqueInput{ID: &userId},
                
      },
        
      }).Exec(ctx)

      
}
func (r *mutationResolver) Publish(ctx context.Context, postId string) (*prisma.Post, error) {
  published := true
               return r.Prisma.UpdatePost(prisma.PostUpdateParams{
                  Where: prisma.PostWhereUniqueInput{ID: &postId},
                      Data:  prisma.PostUpdateInput{Published: &published},
                        
                   }).Exec(ctx)

                   
}

type postResolver struct{ *Resolver  }

func (r *postResolver) Author(ctx context.Context, obj *prisma.Post) (*prisma.User, error) {
  return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &obj.ID}).Author().Exec(ctx)

  
}

type queryResolver struct{ *Resolver  }

func (r *queryResolver) PublishedPosts(ctx context.Context) ([]*prisma.Post, error) {
  published := true
               posts, err := r.Prisma.Posts(&prisma.PostsParams{
                  Where: &prisma.PostWhereInput{Published: &published},
                    
                   }).Exec(ctx)

             if err != nil {
                panic(err)
                  
             }

              postsPointers := make([]*prisma.Post, 0)
                               for _, p := range posts {
                                  postsPointers = append(postsPointers, &p)
                                    
                               }

                                return postsPointers, err

                                
}
func (r *queryResolver) Post(ctx context.Context, postId string) (*prisma.Post, error) {
  return r.Prisma.Post(prisma.PostWhereUniqueInput{ID: &postId}).Exec(ctx)

  
}
func (r *queryResolver) PostsByUser(ctx context.Context, userId string) ([]prisma.Post, error) {
  return r.Prisma.Posts(&prisma.PostsParams{
Where: &prisma.PostWhereInput{
Author: &prisma.UserWhereInput{
        ID: &userId,
              
},
    
},
  
      }).Exec(ctx)

      
}

type userResolver struct{ *Resolver  }

func (r *userResolver) Posts(ctx context.Context, obj *prisma.User) ([]prisma.Post, error) {
  return r.Prisma.User(prisma.UserWhereUniqueInput{ID: &obj.ID}).Posts(nil).Exec(ctx)
}

Now in the “server” directory create a file named “server.go” and add the following:

package main

import (
    "log"
    "net/http"
    "os"

    "hello-world/generated/prisma-client"

    "github.com/99designs/gqlgen/handler"
)

const defaultPort = "4000"

func main() {
    port := os.Getenv("PORT")
    if len(port) == 0 {
        port = defaultPort
    }

    client := prisma.New(nil)
    resolver := Resolver{
        Prisma: client,
    }

    http.Handle("/", handler.Playground("GraphQL Playground", "/query"))
    http.Handle("/query", handler.GraphQL(NewExecutableSchema(Config{Resolvers: &resolver})))

    log.Printf("Server is running on http://localhost:%s", port)
    err := http.ListenAndServe(":"+port, nil)
    if err != nil {
        log.Fatal(err)
    }
}

Start the graphql server

Update the package name at the top of generated.go from package server to “package main”

In generated.go I had to fix a couple of misplaced pointer issues that gqlgen generated. - If you do this when you try to use “PublishedPosts” it will error out, you will need to convert the output of the code to a slice of pointers. I will add the code below. NOTE: Besides changing the package name of the generated.go class to main from server, you should probably not alter this file. You should convert the resolver methods to a slice of pointers. I did this because Prisma provided the implementation and I assumed that was correct, and changed the generated code. That is wrong; but at this point I was just trying to get this tutorial to run.

PostsByUser(ctx context.Context, userID string) ([]prisma.Post, error) -> remove the asterik(pointer) from the [] so it should look like []prisma.Post not []prisma.Post PublishedPosts(ctx context.Context) ([]prisma.Post, error) -> remove the asterik(pointer) from the [] so it should look like []prisma.Post not []prisma.Post Posts(ctx context.Context, obj prisma.User) ([]prisma.Post, error) -> remove the asterik(pointer) from the [] so it should look like []prisma.Post not []*prisma.Post

func (r *queryResolver) PublishedPosts(ctx context.Context) ([]*prisma.Post, error) {
	published := true
	posts, err := r.Prisma.Posts(&prisma.PostsParams{
		Where: &prisma.PostWhereInput{Published: &published},
	}).Exec(ctx)

	if err != nil {
		panic(err)
	}

  // convert what the client returns to a slice of pointers
	postsPointers := make([]*prisma.Post, 0)
	for _, p := range posts {
		postsPointers = append(postsPointers, &p)
	}

	return postsPointers, err

}

go to the root directory - hello-world-prisma and run the following -

go run ./server

you should see this

019/10/30 22:36:41 Server is running on http://localhost:4000

To test these operations, navigate your browser to http://localhost:4000 where a GraphQL Playground is running.

Some operations to use in the playground:

Create a new user

mutation {
  createUser(name: "Bob") {
    id
  }
}

Create a new draft

//__USER_ID__ is the id number of the user
mutation {
  createDraft(title: "GraphQL is great", userId: "__USER_ID__") {
    id
    published
    author {
      id
    }
  }
}

Publish a post

//__POST_ID__ is the id number of the post
mutation {
  publish(id: "__POST_ID__") {
    id
    title
    published
  }
  
}

Fetch Published Posts

query {
  publishedPosts {
      id
      title
      author {
        id
        name
      }
  }
}

All code is located in https://github.com/calam1/prisma-golang-tutorial-blog