Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Pursuing Singularity

Pursuing Singularity

Overview of tools that provide end-to-end type safety across database, API and client in GraphQL ecosystem.

Avatar for Roman Paradeev

Roman Paradeev

October 10, 2019

More Decks by Roman Paradeev

Other Decks in Programming

Transcript

  1. ROMAN PARADEEV ▸ Writing code for ≈10 years ▸ New

    to GraphQL ▸ Software engineer at Klarna 4
  2. 5

  3. 6

  4. 7

  5. 8

  6. // packages/server/lib/resolvers.ts interface User { id: number name: string }

    const resolvers = { Query: { users(): Promise<User[]> { return db.getUsers() } }, Mutation: { createUser(_, args: { name: string }): Promise<User> { return db.createUser(args.name) } } } 10
  7. // packages/client/lib/queries.ts const query = gql`{ users { id name

    } }` // packages/client/lib/types.ts interface Response { users: Array<{ id: number name: string }> } // packages/client/lib/datasource.ts function getUsers() { return client.query<Response>({ query }) } 11
  8. CODE GENERATORS: GETTING STARTED # both client and server npm

    install @graphql-codegen/cli npm install @graphql-codegen/typescript # only client npm install @graphql-codegen/typescript-operations # only server npm install @graphql-codegen/typescript-resolvers 13
  9. CODE GENERATORS: SCRIPT EXECUTION npx lerna exec graphql-codegen # or

    if you don't trust lerna: # > cd packages/client && npx graphql-codegen # > cd packages/server && npx graphql-codegen 16
  10. . !"" client # #"" generated # # $"" types.ts

    # $"" lib # $"" datasources.ts $"" server !"" generated # $"" types.ts $"" lib #"" resolvers.ts $"" schema.ts 17
  11. // packages/server/lib/resolvers.ts interface User { id: number name: string }

    const resolvers = { Query: { users(): Promise<User[]> { return db.getUsers() } }, Mutation: { createUser(_, args: { name: string }): Promise<User> { return db.createUser(args.name) } } } 18
  12. // packages/server/lib/resolvers.ts import { Resolvers } from "../generated/types" const resolvers:

    Resolvers = { Query: { users() { return db.getUsers() } }, Mutation: { createUser(_, args) { return db.createUser(args.name) } } } 19
  13. // packages/client/lib/queries.ts const query = gql`{ users { id name

    } }` // packages/client/lib/types.ts interface Response { users: Array<{ id: number name: string }> } // packages/client/lib/datasource.ts function getUsers() { return client.query<Response>({ query }) } 20
  14. // packages/client/lib/queries.ts const query = gql` query GetUsers { users

    { id name } } ` // packages/client/lib/datasource.ts import { GetUsersQuery } from "../generated/types" function getUsers() { return client.query<GetUsersQuery>({ query }) } 21
  15. 22

  16. // packages/server/lib/datasources.ts interface DbUser { id: number name: string }

    function getUsers() { return knex.select().from<DbUser>("user") } function createUser(name: string) { return knex<DbUser>("user").insert([{ name }]) } 24
  17. // packages/client/lib/queries.ts const query = gql` query GetUsers { users

    { id name } } ` // packages/client/lib/datasource.ts import { GetUsersQuery } from "../generated/types" function getUsers() { return client.query<GetUsersQuery>({ query }) } 25
  18. CLIENT GENERATORS: CONFIGURATION // packages/server/prisma/schema.prisma datasource db { provider =

    "sqlite" url = "file:dev.db" } generator photon { provider = "photonjs" } model User { id String @default(cuid()) @id @unique name String } 29
  19. CLIENT GENERATORS: SCRIPT EXECUTION # server npx prisma2 dev #

    client npx zeus ../server/generated/schema.graphql ./generated --ts 30
  20. . !"" client # $"" generated # $"" graphql-zeus.ts $""

    server !"" generated # $"" schema.graphql $"" node_modules # $"" @generated # $"" photon $"" prisma $""schema.prisma 31
  21. // packages/server/lib/datasources.ts interface DbUser { id: number name: string }

    function getUsers() { return knex.select().from<DbUser>("user") } function createUser(name: string) { return knex<DbUser>("user").insert([{ name }]) } 32
  22. // packages/server/lib/datasources.ts import Photon from "@generated/photon" const photon = new

    Photon() function getUsers() { return photon.users.findMany() } function createUser(name: string) { return photon.users.create({ data: { name } }) } 33
  23. // packages/client/lib/queries.ts const query = gql` query GetUsers { users

    { id name } } ` // packages/client/lib/datasource.ts import { GetUsersQuery } from "../generated/types" function getUsers() { return client.query<GetUsersQuery>({ query }) } 35
  24. // packages/client/lib/datasource.ts import { Api, User } from "../generated/graphql-zeus" const

    client = Api("http://localhost:5000") export async function getUsers() { const users = await client.Query.users([{}, { id: true, name: true }]) return users as Required<User>[] } 36
  25. 37

  26. // packages/server/lib/schema.ts const schema = gql` type Query { users:

    [User!]! } ` // packages/server/lib/resolvers.ts const resolvers: Resolvers = { Query: { users() { return db.getUsers() } } } 38
  27. // packages/server/lib/schema.ts const schema = gql` type Query { users:

    [User!]! } ` // packages/server/lib/resolvers.ts const resolvers: Resolvers = { Query: { users(): Promise<User[]> { return db.getUsers() } } } 41
  28. // packages/server/lib/resolvers.ts import { objectType, queryType, makeSchema } from "nexus"

    const Query = queryType({ definition(t) { t.crud.users() } }) const User = objectType({ name: "User", definition(t) { t.model.id() t.model.name() } }) const schema = makeSchema({ types: [Query, User] }) 42
  29. 43

  30. 44

  31. N + 1 PROBLEM photon.users.findMany({ first: 3, include: { comments:

    true } }) will result in N + 1 SQL queries SELECT * from users SELECT * from comments where user_id = 1 SELECT * from comments where user_id = 2 SELECT * from comments where user_id = 3 46
  32. // packages/server/lib/datasources.ts import Photon from "@generated/photon" const photon = new

    Photon() function getUsers() { return photon.users.findMany() } function createUser(name: string) { return photon.users.create({ data: { name } }) } 48
  33. // packages/server/lib/datasources.ts import Photon, { User } from "@generated/photon" const

    photon = new Photon() function getUsers() { return knex.select().from<User>("user") } function createUser(name: string) { return photon.users.create({ data: { name } }) } 49
  34. 50

  35. 51

  36. SEPARATE YOUR CONCERNS! 1. Separate database schema from API schema

    2. Separate data sources from resolvers 3. Write integrational tests 53
  37. CODE GENERATION TOOLS - type - Apollo CLI - GraphQL

    Generator - SQL client - Prisma / Prisma 2 - Hasura - PostGraphile - GraphQL client - GraphQL Zeus 55