├── .gitignore ├── README.md └── packages ├── graphql-with-prisma-relay ├── .gitignore ├── README.md ├── api │ ├── index.ts │ └── serverful.ts ├── nexus-prisma-typegen.ts ├── nexus-typegen.ts ├── package.json ├── prisma │ ├── migrations │ │ ├── 20200729172424-init │ │ │ ├── README.md │ │ │ ├── schema.prisma │ │ │ └── steps.json │ │ ├── 20200729185831-votes-default │ │ │ ├── README.md │ │ │ ├── schema.prisma │ │ │ └── steps.json │ │ ├── 20200731151859-blog-post │ │ │ ├── README.md │ │ │ ├── schema.prisma │ │ │ └── steps.json │ │ └── migrate.lock │ ├── schema.prisma │ └── seed.ts ├── schema.graphql ├── schema │ ├── BlogPost.ts │ ├── Meta.ts │ ├── Node.ts │ ├── User.ts │ ├── Viewer.ts │ ├── context.ts │ ├── definitions.ts │ ├── index.ts │ └── utils.ts ├── services │ ├── Post.ts │ ├── User.ts │ ├── index.ts │ └── utils.ts ├── tsconfig.json └── yarn.lock └── graphql-with-prisma-simple ├── .gitignore ├── README.md ├── api ├── index.ts └── serverful.ts ├── nexus-prisma-typegen.ts ├── nexus-typegen.ts ├── package.json ├── prisma ├── migrations │ ├── 20200729172424-init │ │ ├── README.md │ │ ├── schema.prisma │ │ └── steps.json │ ├── 20200729185831-votes-default │ │ ├── README.md │ │ ├── schema.prisma │ │ └── steps.json │ └── migrate.lock ├── schema.prisma └── seed.ts ├── schema.graphql ├── schema ├── Meta.ts ├── Post.ts ├── User.ts ├── context.ts ├── definitions.ts ├── index.ts └── utils.ts ├── services ├── Post.ts ├── User.ts ├── index.ts └── utils.ts ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vercel -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GraphQL APIs for Next.js examples 2 | 3 | This repository contains GraphQL APIs that are used in the [Next.js examples](https://github.com/vercel/next.js/tree/canary/examples). They use [Prisma](http://prisma.io/) + [Nexus](http://nexusjs.org/) and are hosted on [Vercel](https://vercel.com/) as: 4 | 5 | 6 | | example | url | 7 | |------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------| 8 | | [graphql-with-prisma-simple](https://github.com/prisma-labs/nextjs-graphql-api-examples/tree/master/packages/graphql-with-prisma-simple) | https://nextjs-graphql-with-prisma-simple-foo.vercel.app/api | 9 | | [graphql-with-prisma-relay](https://github.com/prisma-labs/nextjs-graphql-api-examples/tree/master/packages/graphql-with-prisma-relay) | https://nextjs-graphql-with-prisma-relay.vercel.app/api | 10 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .env -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/README.md: -------------------------------------------------------------------------------- 1 | # graphql-with-prisma-relay 2 | 3 | ### Try It 4 | 5 | ``` 6 | yarn && yarn dev 7 | ``` 8 | 9 | ## Deploy your own 10 | 11 | Deploy the example using [Vercel](https://vercel.com): 12 | 13 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/prisma-labs/nextjs-graphql-api-examples/tree/master/packages/graphql-with-prisma-relay) 14 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/api/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { graphqlHTTP } from "express-graphql" 3 | import schema from "../schema" 4 | import { buildServices } from "../services" 5 | 6 | const prisma = new PrismaClient() 7 | 8 | const allowCors = (fn: Function) => async (req: any, res: any) => { 9 | res.setHeader('Access-Control-Allow-Credentials', true) 10 | res.setHeader('Access-Control-Allow-Origin', '*') 11 | // another option 12 | // res.setHeader('Access-Control-Allow-Origin', req.headers.origin); 13 | res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS') 14 | res.setHeader( 15 | 'Access-Control-Allow-Headers', 16 | 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' 17 | ) 18 | if (req.method === 'OPTIONS') { 19 | res.status(200).end() 20 | return 21 | } 22 | return await fn(req, res) 23 | } 24 | 25 | export const handler = graphqlHTTP(_ => ({ 26 | schema, 27 | graphiql: true, 28 | pretty: true, 29 | context: { 30 | ...buildServices(prisma) 31 | }, 32 | })) 33 | 34 | export default allowCors(handler) 35 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/api/serverful.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { ApolloServer } from "apollo-server" 3 | import schema from "../schema" 4 | import { buildServices } from "../services" 5 | 6 | const prisma = new PrismaClient() 7 | 8 | const server = new ApolloServer({ 9 | schema, 10 | context: () => { 11 | return buildServices(prisma) 12 | }, 13 | }) 14 | 15 | server.listen().then(({ url }) => { 16 | console.log(`🚀 Server ready at ${url}`) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/nexus-prisma-typegen.ts: -------------------------------------------------------------------------------- 1 | import * as Typegen from 'nexus-plugin-prisma/typegen' 2 | import * as Prisma from '@prisma/client'; 3 | 4 | // Pagination type 5 | type Pagination = { 6 | first?: boolean 7 | last?: boolean 8 | before?: boolean 9 | after?: boolean 10 | } 11 | 12 | // Prisma custom scalar names 13 | type CustomScalars = 'DateTime' 14 | 15 | // Prisma model type definitions 16 | interface PrismaModels { 17 | BlogPost: Prisma.BlogPost 18 | User: Prisma.User 19 | } 20 | 21 | // Prisma input types metadata 22 | interface NexusPrismaInputs { 23 | Query: { 24 | blogPosts: { 25 | filtering: 'id' | 'title' | 'content' | 'createdAt' | 'updatedAt' | 'AND' | 'OR' | 'NOT' 26 | ordering: 'id' | 'title' | 'content' | 'createdAt' | 'updatedAt' 27 | } 28 | users: { 29 | filtering: 'id' | 'createdAt' | 'updatedAt' | 'email' | 'firstName' | 'lastName' | 'password' | 'AND' | 'OR' | 'NOT' 30 | ordering: 'id' | 'createdAt' | 'updatedAt' | 'email' | 'firstName' | 'lastName' | 'password' 31 | } 32 | }, 33 | BlogPost: { 34 | 35 | } 36 | User: { 37 | 38 | } 39 | } 40 | 41 | // Prisma output types metadata 42 | interface NexusPrismaOutputs { 43 | Query: { 44 | blogPost: 'BlogPost' 45 | blogPosts: 'BlogPost' 46 | user: 'User' 47 | users: 'User' 48 | }, 49 | Mutation: { 50 | createOneBlogPost: 'BlogPost' 51 | updateOneBlogPost: 'BlogPost' 52 | updateManyBlogPost: 'BatchPayload' 53 | deleteOneBlogPost: 'BlogPost' 54 | deleteManyBlogPost: 'BatchPayload' 55 | upsertOneBlogPost: 'BlogPost' 56 | createOneUser: 'User' 57 | updateOneUser: 'User' 58 | updateManyUser: 'BatchPayload' 59 | deleteOneUser: 'User' 60 | deleteManyUser: 'BatchPayload' 61 | upsertOneUser: 'User' 62 | }, 63 | BlogPost: { 64 | id: 'String' 65 | title: 'String' 66 | content: 'String' 67 | createdAt: 'DateTime' 68 | updatedAt: 'DateTime' 69 | } 70 | User: { 71 | id: 'String' 72 | createdAt: 'DateTime' 73 | updatedAt: 'DateTime' 74 | email: 'String' 75 | firstName: 'String' 76 | lastName: 'String' 77 | password: 'String' 78 | } 79 | } 80 | 81 | // Helper to gather all methods relative to a model 82 | interface NexusPrismaMethods { 83 | BlogPost: Typegen.NexusPrismaFields<'BlogPost'> 84 | User: Typegen.NexusPrismaFields<'User'> 85 | Query: Typegen.NexusPrismaFields<'Query'> 86 | Mutation: Typegen.NexusPrismaFields<'Mutation'> 87 | } 88 | 89 | interface NexusPrismaGenTypes { 90 | inputs: NexusPrismaInputs 91 | outputs: NexusPrismaOutputs 92 | methods: NexusPrismaMethods 93 | models: PrismaModels 94 | pagination: Pagination 95 | scalars: CustomScalars 96 | } 97 | 98 | declare global { 99 | interface NexusPrismaGen extends NexusPrismaGenTypes {} 100 | 101 | type NexusPrisma< 102 | TypeName extends string, 103 | ModelOrCrud extends 'model' | 'crud' 104 | > = Typegen.GetNexusPrisma; 105 | } 106 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/nexus-typegen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was generated by Nexus Schema 3 | * Do not make changes to this file directly 4 | */ 5 | 6 | import * as ctx from "./schema/context" 7 | import * as prisma from "./node_modules/.prisma/client/index" 8 | import { core, connectionPluginCore } from "@nexus/schema" 9 | 10 | declare global { 11 | interface NexusGenCustomOutputMethods { 12 | connectionField( 13 | fieldName: FieldName, 14 | config: connectionPluginCore.ConnectionFieldConfig 15 | ): void 16 | } 17 | } 18 | declare global { 19 | interface NexusGenCustomOutputProperties { 20 | model: NexusPrisma 21 | crud: any 22 | } 23 | } 24 | 25 | declare global { 26 | interface NexusGen extends NexusGenTypes {} 27 | } 28 | 29 | export interface NexusGenInputs { 30 | BlogPostOrderBy: { // input type 31 | createdAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 32 | updatedAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 33 | } 34 | } 35 | 36 | export interface NexusGenEnums { 37 | OrderBy: "asc" | "desc" 38 | } 39 | 40 | export interface NexusGenRootTypes { 41 | BlogPost: prisma.BlogPost; 42 | BlogPostConnection: { // root type 43 | edges?: Array | null; // [BlogPostEdge] 44 | pageInfo: NexusGenRootTypes['PageInfo']; // PageInfo! 45 | } 46 | BlogPostEdge: { // root type 47 | cursor: string; // String! 48 | node: NexusGenRootTypes['BlogPost']; // BlogPost! 49 | } 50 | Mutation: {}; 51 | PageInfo: { // root type 52 | endCursor?: string | null; // String 53 | hasNextPage: boolean; // Boolean! 54 | hasPreviousPage: boolean; // Boolean! 55 | startCursor?: string | null; // String 56 | } 57 | Query: {}; 58 | User: prisma.User; 59 | UserConnection: { // root type 60 | edges?: Array | null; // [UserEdge] 61 | pageInfo: NexusGenRootTypes['PageInfo']; // PageInfo! 62 | } 63 | UserEdge: { // root type 64 | cursor: string; // String! 65 | node: NexusGenRootTypes['User']; // User! 66 | } 67 | Viewer: {}; 68 | Node: NexusGenRootTypes['BlogPost'] | NexusGenRootTypes['User']; 69 | String: string; 70 | Int: number; 71 | Float: number; 72 | Boolean: boolean; 73 | ID: string; 74 | DateTime: any; 75 | } 76 | 77 | export interface NexusGenAllTypes extends NexusGenRootTypes { 78 | BlogPostOrderBy: NexusGenInputs['BlogPostOrderBy']; 79 | OrderBy: NexusGenEnums['OrderBy']; 80 | } 81 | 82 | export interface NexusGenFieldTypes { 83 | BlogPost: { // field return type 84 | content: string; // String! 85 | createdAt: any; // DateTime! 86 | id: string; // ID! 87 | title: string; // String! 88 | updatedAt: any; // DateTime! 89 | } 90 | BlogPostConnection: { // field return type 91 | edges: Array | null; // [BlogPostEdge] 92 | pageInfo: NexusGenRootTypes['PageInfo']; // PageInfo! 93 | } 94 | BlogPostEdge: { // field return type 95 | cursor: string; // String! 96 | node: NexusGenRootTypes['BlogPost']; // BlogPost! 97 | } 98 | Mutation: { // field return type 99 | createBlogPost: NexusGenRootTypes['BlogPost']; // BlogPost! 100 | } 101 | PageInfo: { // field return type 102 | endCursor: string | null; // String 103 | hasNextPage: boolean; // Boolean! 104 | hasPreviousPage: boolean; // Boolean! 105 | startCursor: string | null; // String 106 | } 107 | Query: { // field return type 108 | node: NexusGenRootTypes['Node']; // Node! 109 | viewer: NexusGenRootTypes['Viewer']; // Viewer! 110 | } 111 | User: { // field return type 112 | createdAt: any; // DateTime! 113 | email: string; // String! 114 | firstName: string; // String! 115 | id: string; // ID! 116 | lastName: string; // String! 117 | updatedAt: any; // DateTime! 118 | } 119 | UserConnection: { // field return type 120 | edges: Array | null; // [UserEdge] 121 | pageInfo: NexusGenRootTypes['PageInfo']; // PageInfo! 122 | } 123 | UserEdge: { // field return type 124 | cursor: string; // String! 125 | node: NexusGenRootTypes['User']; // User! 126 | } 127 | Viewer: { // field return type 128 | allBlogPosts: NexusGenRootTypes['BlogPostConnection']; // BlogPostConnection! 129 | allUsers: NexusGenRootTypes['UserConnection']; // UserConnection! 130 | BlogPost: NexusGenRootTypes['BlogPost']; // BlogPost! 131 | User: NexusGenRootTypes['User']; // User! 132 | } 133 | Node: { // field return type 134 | id: string; // ID! 135 | } 136 | } 137 | 138 | export interface NexusGenArgTypes { 139 | Mutation: { 140 | createBlogPost: { // args 141 | content: string; // String! 142 | title: string; // String! 143 | } 144 | } 145 | Query: { 146 | node: { // args 147 | id: string; // String! 148 | } 149 | } 150 | Viewer: { 151 | allBlogPosts: { // args 152 | after?: string | null; // String 153 | first: number; // Int! 154 | orderBy?: NexusGenInputs['BlogPostOrderBy'] | null; // BlogPostOrderBy 155 | } 156 | allUsers: { // args 157 | after?: string | null; // String 158 | first: number; // Int! 159 | } 160 | BlogPost: { // args 161 | id: string; // String! 162 | } 163 | User: { // args 164 | id: string; // String! 165 | } 166 | } 167 | } 168 | 169 | export interface NexusGenAbstractResolveReturnTypes { 170 | Node: "BlogPost" | "User" 171 | } 172 | 173 | export interface NexusGenInheritedFields {} 174 | 175 | export type NexusGenObjectNames = "BlogPost" | "BlogPostConnection" | "BlogPostEdge" | "Mutation" | "PageInfo" | "Query" | "User" | "UserConnection" | "UserEdge" | "Viewer"; 176 | 177 | export type NexusGenInputNames = "BlogPostOrderBy"; 178 | 179 | export type NexusGenEnumNames = "OrderBy"; 180 | 181 | export type NexusGenInterfaceNames = "Node"; 182 | 183 | export type NexusGenScalarNames = "Boolean" | "DateTime" | "Float" | "ID" | "Int" | "String"; 184 | 185 | export type NexusGenUnionNames = never; 186 | 187 | export interface NexusGenTypes { 188 | context: ctx.Context; 189 | inputTypes: NexusGenInputs; 190 | rootTypes: NexusGenRootTypes; 191 | argTypes: NexusGenArgTypes; 192 | fieldTypes: NexusGenFieldTypes; 193 | allTypes: NexusGenAllTypes; 194 | inheritedFields: NexusGenInheritedFields; 195 | objectNames: NexusGenObjectNames; 196 | inputNames: NexusGenInputNames; 197 | enumNames: NexusGenEnumNames; 198 | interfaceNames: NexusGenInterfaceNames; 199 | scalarNames: NexusGenScalarNames; 200 | unionNames: NexusGenUnionNames; 201 | allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames']; 202 | allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames']; 203 | allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes'] 204 | abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames']; 205 | abstractResolveReturn: NexusGenAbstractResolveReturnTypes; 206 | } 207 | 208 | 209 | declare global { 210 | interface NexusGenPluginTypeConfig { 211 | } 212 | interface NexusGenPluginFieldConfig { 213 | 214 | } 215 | interface NexusGenPluginSchemaConfig { 216 | } 217 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zeit-typescript", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "yarn ts-node-dev --transpile-only ./api/serverful.ts", 8 | "vercel:dev": "vercel dev", 9 | "generate:nexus": "ts-node --transpile-only schema", 10 | "now-build": "yarn prisma generate && yarn -s generate:nexus", 11 | "seed": "yarn ts-node prisma/seed.ts" 12 | }, 13 | "prettier": { 14 | "semi": false, 15 | "trailingComma": "all" 16 | }, 17 | "dependencies": { 18 | "@nexus/schema": "^0.14.0", 19 | "apollo-server": "^2.16.1", 20 | "express-graphql": "^0.11.0", 21 | "graphql": "^14.5.8", 22 | "graphql-relay": "^0.6.0", 23 | "nexus-plugin-prisma": "^0.16.1" 24 | }, 25 | "devDependencies": { 26 | "@now/node": "^1.0.1", 27 | "@types/faker": "^4.1.12", 28 | "@types/graphql-relay": "^0.6.0", 29 | "@types/node": "^12.11.1", 30 | "faker": "^4.1.0", 31 | "ts-node": "^8.4.1", 32 | "ts-node-dev": "^1.0.0-pre.56", 33 | "typescript": "^3.9.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729172424-init/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200729172424-init` 2 | 3 | This migration has been generated by Flavian DESVERNE at 7/29/2020, 5:24:24 PM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | PRAGMA foreign_keys=OFF; 10 | 11 | CREATE TABLE "quaint"."Post" ( 12 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"id" TEXT NOT NULL ,"title" TEXT NOT NULL ,"updatedAt" DATE NOT NULL ,"url" TEXT NOT NULL ,"votes" INTEGER NOT NULL , 13 | PRIMARY KEY ("id")) 14 | 15 | CREATE TABLE "quaint"."User" ( 16 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"email" TEXT NOT NULL ,"firstName" TEXT NOT NULL ,"id" TEXT NOT NULL ,"lastName" TEXT NOT NULL ,"password" TEXT NOT NULL ,"updatedAt" DATE NOT NULL , 17 | PRIMARY KEY ("id")) 18 | 19 | PRAGMA "quaint".foreign_key_check; 20 | 21 | PRAGMA foreign_keys=ON; 22 | ``` 23 | 24 | ## Changes 25 | 26 | ```diff 27 | diff --git schema.prisma schema.prisma 28 | migration ..20200729172424-init 29 | --- datamodel.dml 30 | +++ datamodel.dml 31 | @@ -1,0 +1,30 @@ 32 | +// This is your Prisma schema file, 33 | +// learn more about it in the docs: https://pris.ly/d/prisma-schema 34 | + 35 | +datasource db { 36 | + provider = "sqlite" 37 | + url = "***" 38 | +} 39 | + 40 | +generator client { 41 | + provider = "prisma-client-js" 42 | +} 43 | + 44 | +model Post { 45 | + id String @id @default(uuid()) 46 | + title String 47 | + createdAt DateTime @default(now()) 48 | + updatedAt DateTime @updatedAt 49 | + url String 50 | + votes Int 51 | +} 52 | + 53 | +model User { 54 | + id String @id @default(uuid()) 55 | + createdAt DateTime @default(now()) 56 | + updatedAt DateTime @updatedAt 57 | + email String 58 | + firstName String 59 | + lastName String 60 | + password String 61 | +} 62 | ``` 63 | 64 | 65 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729172424-init/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "sqlite" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model Post { 14 | id String @id @default(uuid()) 15 | title String 16 | createdAt DateTime @default(now()) 17 | updatedAt DateTime @updatedAt 18 | url String 19 | votes Int 20 | } 21 | 22 | model User { 23 | id String @id @default(uuid()) 24 | createdAt DateTime @default(now()) 25 | updatedAt DateTime @updatedAt 26 | email String 27 | firstName String 28 | lastName String 29 | password String 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729172424-init/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateSource", 6 | "source": "db" 7 | }, 8 | { 9 | "tag": "CreateArgument", 10 | "location": { 11 | "tag": "Source", 12 | "source": "db" 13 | }, 14 | "argument": "provider", 15 | "value": "\"sqlite\"" 16 | }, 17 | { 18 | "tag": "CreateArgument", 19 | "location": { 20 | "tag": "Source", 21 | "source": "db" 22 | }, 23 | "argument": "url", 24 | "value": "\"***\"" 25 | }, 26 | { 27 | "tag": "CreateModel", 28 | "model": "Post" 29 | }, 30 | { 31 | "tag": "CreateField", 32 | "model": "Post", 33 | "field": "id", 34 | "type": "String", 35 | "arity": "Required" 36 | }, 37 | { 38 | "tag": "CreateDirective", 39 | "location": { 40 | "path": { 41 | "tag": "Field", 42 | "model": "Post", 43 | "field": "id" 44 | }, 45 | "directive": "id" 46 | } 47 | }, 48 | { 49 | "tag": "CreateDirective", 50 | "location": { 51 | "path": { 52 | "tag": "Field", 53 | "model": "Post", 54 | "field": "id" 55 | }, 56 | "directive": "default" 57 | } 58 | }, 59 | { 60 | "tag": "CreateArgument", 61 | "location": { 62 | "tag": "Directive", 63 | "path": { 64 | "tag": "Field", 65 | "model": "Post", 66 | "field": "id" 67 | }, 68 | "directive": "default" 69 | }, 70 | "argument": "", 71 | "value": "uuid()" 72 | }, 73 | { 74 | "tag": "CreateField", 75 | "model": "Post", 76 | "field": "title", 77 | "type": "String", 78 | "arity": "Required" 79 | }, 80 | { 81 | "tag": "CreateField", 82 | "model": "Post", 83 | "field": "createdAt", 84 | "type": "DateTime", 85 | "arity": "Required" 86 | }, 87 | { 88 | "tag": "CreateDirective", 89 | "location": { 90 | "path": { 91 | "tag": "Field", 92 | "model": "Post", 93 | "field": "createdAt" 94 | }, 95 | "directive": "default" 96 | } 97 | }, 98 | { 99 | "tag": "CreateArgument", 100 | "location": { 101 | "tag": "Directive", 102 | "path": { 103 | "tag": "Field", 104 | "model": "Post", 105 | "field": "createdAt" 106 | }, 107 | "directive": "default" 108 | }, 109 | "argument": "", 110 | "value": "now()" 111 | }, 112 | { 113 | "tag": "CreateField", 114 | "model": "Post", 115 | "field": "updatedAt", 116 | "type": "DateTime", 117 | "arity": "Required" 118 | }, 119 | { 120 | "tag": "CreateDirective", 121 | "location": { 122 | "path": { 123 | "tag": "Field", 124 | "model": "Post", 125 | "field": "updatedAt" 126 | }, 127 | "directive": "updatedAt" 128 | } 129 | }, 130 | { 131 | "tag": "CreateField", 132 | "model": "Post", 133 | "field": "url", 134 | "type": "String", 135 | "arity": "Required" 136 | }, 137 | { 138 | "tag": "CreateField", 139 | "model": "Post", 140 | "field": "votes", 141 | "type": "Int", 142 | "arity": "Required" 143 | }, 144 | { 145 | "tag": "CreateModel", 146 | "model": "User" 147 | }, 148 | { 149 | "tag": "CreateField", 150 | "model": "User", 151 | "field": "id", 152 | "type": "String", 153 | "arity": "Required" 154 | }, 155 | { 156 | "tag": "CreateDirective", 157 | "location": { 158 | "path": { 159 | "tag": "Field", 160 | "model": "User", 161 | "field": "id" 162 | }, 163 | "directive": "id" 164 | } 165 | }, 166 | { 167 | "tag": "CreateDirective", 168 | "location": { 169 | "path": { 170 | "tag": "Field", 171 | "model": "User", 172 | "field": "id" 173 | }, 174 | "directive": "default" 175 | } 176 | }, 177 | { 178 | "tag": "CreateArgument", 179 | "location": { 180 | "tag": "Directive", 181 | "path": { 182 | "tag": "Field", 183 | "model": "User", 184 | "field": "id" 185 | }, 186 | "directive": "default" 187 | }, 188 | "argument": "", 189 | "value": "uuid()" 190 | }, 191 | { 192 | "tag": "CreateField", 193 | "model": "User", 194 | "field": "createdAt", 195 | "type": "DateTime", 196 | "arity": "Required" 197 | }, 198 | { 199 | "tag": "CreateDirective", 200 | "location": { 201 | "path": { 202 | "tag": "Field", 203 | "model": "User", 204 | "field": "createdAt" 205 | }, 206 | "directive": "default" 207 | } 208 | }, 209 | { 210 | "tag": "CreateArgument", 211 | "location": { 212 | "tag": "Directive", 213 | "path": { 214 | "tag": "Field", 215 | "model": "User", 216 | "field": "createdAt" 217 | }, 218 | "directive": "default" 219 | }, 220 | "argument": "", 221 | "value": "now()" 222 | }, 223 | { 224 | "tag": "CreateField", 225 | "model": "User", 226 | "field": "updatedAt", 227 | "type": "DateTime", 228 | "arity": "Required" 229 | }, 230 | { 231 | "tag": "CreateDirective", 232 | "location": { 233 | "path": { 234 | "tag": "Field", 235 | "model": "User", 236 | "field": "updatedAt" 237 | }, 238 | "directive": "updatedAt" 239 | } 240 | }, 241 | { 242 | "tag": "CreateField", 243 | "model": "User", 244 | "field": "email", 245 | "type": "String", 246 | "arity": "Required" 247 | }, 248 | { 249 | "tag": "CreateField", 250 | "model": "User", 251 | "field": "firstName", 252 | "type": "String", 253 | "arity": "Required" 254 | }, 255 | { 256 | "tag": "CreateField", 257 | "model": "User", 258 | "field": "lastName", 259 | "type": "String", 260 | "arity": "Required" 261 | }, 262 | { 263 | "tag": "CreateField", 264 | "model": "User", 265 | "field": "password", 266 | "type": "String", 267 | "arity": "Required" 268 | } 269 | ] 270 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729185831-votes-default/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200729185831-votes-default` 2 | 3 | This migration has been generated by Flavian DESVERNE at 7/29/2020, 6:58:31 PM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | PRAGMA foreign_keys=OFF; 10 | 11 | CREATE TABLE "quaint"."new_Post" ( 12 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"id" TEXT NOT NULL ,"title" TEXT NOT NULL ,"updatedAt" DATE NOT NULL ,"url" TEXT NOT NULL ,"votes" INTEGER NOT NULL DEFAULT 0 , 13 | PRIMARY KEY ("id")) 14 | 15 | INSERT INTO "quaint"."new_Post" ("createdAt", "id", "title", "updatedAt", "url", "votes") SELECT "createdAt", "id", "title", "updatedAt", "url", "votes" FROM "quaint"."Post" 16 | 17 | PRAGMA foreign_keys=off; 18 | DROP TABLE "quaint"."Post";; 19 | PRAGMA foreign_keys=on 20 | 21 | ALTER TABLE "quaint"."new_Post" RENAME TO "Post"; 22 | 23 | PRAGMA "quaint".foreign_key_check; 24 | 25 | PRAGMA foreign_keys=ON; 26 | ``` 27 | 28 | ## Changes 29 | 30 | ```diff 31 | diff --git schema.prisma schema.prisma 32 | migration 20200729172424-init..20200729185831-votes-default 33 | --- datamodel.dml 34 | +++ datamodel.dml 35 | @@ -2,22 +2,23 @@ 36 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 37 | datasource db { 38 | provider = "sqlite" 39 | - url = "***" 40 | + url = "***" 41 | } 42 | generator client { 43 | - provider = "prisma-client-js" 44 | + provider = "prisma-client-js" 45 | + previewFeatures = ["transactionApi"] 46 | } 47 | model Post { 48 | id String @id @default(uuid()) 49 | title String 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @updatedAt 52 | url String 53 | - votes Int 54 | + votes Int @default(0) 55 | } 56 | model User { 57 | id String @id @default(uuid()) 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729185831-votes-default/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "sqlite" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | previewFeatures = ["transactionApi"] 12 | } 13 | 14 | model Post { 15 | id String @id @default(uuid()) 16 | title String 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | url String 20 | votes Int @default(0) 21 | } 22 | 23 | model User { 24 | id String @id @default(uuid()) 25 | createdAt DateTime @default(now()) 26 | updatedAt DateTime @updatedAt 27 | email String 28 | firstName String 29 | lastName String 30 | password String 31 | } 32 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200729185831-votes-default/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateDirective", 6 | "location": { 7 | "path": { 8 | "tag": "Field", 9 | "model": "Post", 10 | "field": "votes" 11 | }, 12 | "directive": "default" 13 | } 14 | }, 15 | { 16 | "tag": "CreateArgument", 17 | "location": { 18 | "tag": "Directive", 19 | "path": { 20 | "tag": "Field", 21 | "model": "Post", 22 | "field": "votes" 23 | }, 24 | "directive": "default" 25 | }, 26 | "argument": "", 27 | "value": "0" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200731151859-blog-post/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200731151859-blog-post` 2 | 3 | This migration has been generated by Flavian DESVERNE at 7/31/2020, 3:18:59 PM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | CREATE TABLE "public"."BlogPost" ( 10 | "content" text NOT NULL ,"createdAt" timestamp(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,"id" text NOT NULL ,"title" text NOT NULL ,"updatedAt" timestamp(3) NOT NULL , 11 | PRIMARY KEY ("id")) 12 | 13 | DROP TABLE "public"."Post"; 14 | ``` 15 | 16 | ## Changes 17 | 18 | ```diff 19 | diff --git schema.prisma schema.prisma 20 | migration 20200729185831-votes-default..20200731151859-blog-post 21 | --- datamodel.dml 22 | +++ datamodel.dml 23 | @@ -1,24 +1,22 @@ 24 | // This is your Prisma schema file, 25 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 26 | datasource db { 27 | - provider = "sqlite" 28 | - url = "***" 29 | + provider = "postgres" 30 | + url = "***" 31 | } 32 | generator client { 33 | provider = "prisma-client-js" 34 | - previewFeatures = ["transactionApi"] 35 | } 36 | -model Post { 37 | +model BlogPost { 38 | id String @id @default(uuid()) 39 | title String 40 | + content String 41 | createdAt DateTime @default(now()) 42 | updatedAt DateTime @updatedAt 43 | - url String 44 | - votes Int @default(0) 45 | } 46 | model User { 47 | id String @id @default(uuid()) 48 | ``` 49 | 50 | 51 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200731151859-blog-post/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgres" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model BlogPost { 14 | id String @id @default(uuid()) 15 | title String 16 | content String 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | } 20 | 21 | model User { 22 | id String @id @default(uuid()) 23 | createdAt DateTime @default(now()) 24 | updatedAt DateTime @updatedAt 25 | email String 26 | firstName String 27 | lastName String 28 | password String 29 | } 30 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/20200731151859-blog-post/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "UpdateArgument", 6 | "location": { 7 | "tag": "Source", 8 | "source": "db" 9 | }, 10 | "argument": "provider", 11 | "newValue": "\"postgres\"" 12 | }, 13 | { 14 | "tag": "CreateModel", 15 | "model": "BlogPost" 16 | }, 17 | { 18 | "tag": "CreateField", 19 | "model": "BlogPost", 20 | "field": "id", 21 | "type": "String", 22 | "arity": "Required" 23 | }, 24 | { 25 | "tag": "CreateDirective", 26 | "location": { 27 | "path": { 28 | "tag": "Field", 29 | "model": "BlogPost", 30 | "field": "id" 31 | }, 32 | "directive": "id" 33 | } 34 | }, 35 | { 36 | "tag": "CreateDirective", 37 | "location": { 38 | "path": { 39 | "tag": "Field", 40 | "model": "BlogPost", 41 | "field": "id" 42 | }, 43 | "directive": "default" 44 | } 45 | }, 46 | { 47 | "tag": "CreateArgument", 48 | "location": { 49 | "tag": "Directive", 50 | "path": { 51 | "tag": "Field", 52 | "model": "BlogPost", 53 | "field": "id" 54 | }, 55 | "directive": "default" 56 | }, 57 | "argument": "", 58 | "value": "uuid()" 59 | }, 60 | { 61 | "tag": "CreateField", 62 | "model": "BlogPost", 63 | "field": "title", 64 | "type": "String", 65 | "arity": "Required" 66 | }, 67 | { 68 | "tag": "CreateField", 69 | "model": "BlogPost", 70 | "field": "content", 71 | "type": "String", 72 | "arity": "Required" 73 | }, 74 | { 75 | "tag": "CreateField", 76 | "model": "BlogPost", 77 | "field": "createdAt", 78 | "type": "DateTime", 79 | "arity": "Required" 80 | }, 81 | { 82 | "tag": "CreateDirective", 83 | "location": { 84 | "path": { 85 | "tag": "Field", 86 | "model": "BlogPost", 87 | "field": "createdAt" 88 | }, 89 | "directive": "default" 90 | } 91 | }, 92 | { 93 | "tag": "CreateArgument", 94 | "location": { 95 | "tag": "Directive", 96 | "path": { 97 | "tag": "Field", 98 | "model": "BlogPost", 99 | "field": "createdAt" 100 | }, 101 | "directive": "default" 102 | }, 103 | "argument": "", 104 | "value": "now()" 105 | }, 106 | { 107 | "tag": "CreateField", 108 | "model": "BlogPost", 109 | "field": "updatedAt", 110 | "type": "DateTime", 111 | "arity": "Required" 112 | }, 113 | { 114 | "tag": "CreateDirective", 115 | "location": { 116 | "path": { 117 | "tag": "Field", 118 | "model": "BlogPost", 119 | "field": "updatedAt" 120 | }, 121 | "directive": "updatedAt" 122 | } 123 | }, 124 | { 125 | "tag": "DeleteModel", 126 | "model": "Post" 127 | } 128 | ] 129 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/migrations/migrate.lock: -------------------------------------------------------------------------------- 1 | # Prisma Migrate lockfile v1 2 | 3 | 20200729172424-init 4 | 20200729185831-votes-default 5 | 20200731151859-blog-post -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgres" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model BlogPost { 14 | id String @id @default(uuid()) 15 | title String 16 | content String 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | } 20 | 21 | model User { 22 | id String @id @default(uuid()) 23 | createdAt DateTime @default(now()) 24 | updatedAt DateTime @updatedAt 25 | email String 26 | firstName String 27 | lastName String 28 | password String 29 | } 30 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PrismaClient, 3 | UserCreateInput, 4 | BlogPostCreateInput, 5 | } from "@prisma/client" 6 | import faker from "faker" 7 | 8 | async function seed() { 9 | const prisma = new PrismaClient() 10 | 11 | const userPromises = Array.from(Array(20).keys()).map(_ => { 12 | const firstName = faker.name.firstName() 13 | const lastName = faker.name.lastName() 14 | const user: UserCreateInput = { 15 | firstName, 16 | lastName, 17 | email: faker.internet.exampleEmail(firstName, lastName), 18 | password: faker.random.uuid(), 19 | } 20 | 21 | return prisma.user.create({ 22 | data: user, 23 | }) 24 | }) 25 | 26 | const postPromises = Array.from(Array(30).keys()).map(_ => { 27 | const post: BlogPostCreateInput = { 28 | title: faker.lorem.sentence(), 29 | content: faker.lorem.sentences(3), 30 | } 31 | 32 | return prisma.blogPost.create({ 33 | data: post, 34 | }) 35 | }) 36 | 37 | await Promise.all([...userPromises, ...postPromises] as Promise[]) 38 | 39 | await prisma.disconnect() 40 | 41 | console.log("Seeded db!") 42 | } 43 | 44 | seed() 45 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema.graphql: -------------------------------------------------------------------------------- 1 | ### This file was generated by Nexus Schema 2 | ### Do not make changes to this file directly 3 | 4 | 5 | type BlogPost implements Node { 6 | content: String! 7 | createdAt: DateTime! 8 | id: ID! 9 | title: String! 10 | updatedAt: DateTime! 11 | } 12 | 13 | type BlogPostConnection { 14 | """ 15 | https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types 16 | """ 17 | edges: [BlogPostEdge] 18 | 19 | """ 20 | https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo 21 | """ 22 | pageInfo: PageInfo! 23 | } 24 | 25 | type BlogPostEdge { 26 | """https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor""" 27 | cursor: String! 28 | 29 | """https://facebook.github.io/relay/graphql/connections.htm#sec-Node""" 30 | node: BlogPost! 31 | } 32 | 33 | input BlogPostOrderBy { 34 | createdAt: OrderBy 35 | updatedAt: OrderBy 36 | } 37 | 38 | scalar DateTime 39 | 40 | type Mutation { 41 | createBlogPost(content: String!, title: String!): BlogPost! 42 | } 43 | 44 | interface Node { 45 | id: ID! 46 | } 47 | 48 | enum OrderBy { 49 | asc 50 | desc 51 | } 52 | 53 | """ 54 | PageInfo cursor, as defined in https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo 55 | """ 56 | type PageInfo { 57 | """ 58 | The cursor corresponding to the last nodes in edges. Null if the connection is empty. 59 | """ 60 | endCursor: String 61 | 62 | """ 63 | Used to indicate whether more edges exist following the set defined by the clients arguments. 64 | """ 65 | hasNextPage: Boolean! 66 | 67 | """ 68 | Used to indicate whether more edges exist prior to the set defined by the clients arguments. 69 | """ 70 | hasPreviousPage: Boolean! 71 | 72 | """ 73 | The cursor corresponding to the first nodes in edges. Null if the connection is empty. 74 | """ 75 | startCursor: String 76 | } 77 | 78 | type Query { 79 | node(id: String!): Node! 80 | viewer: Viewer! 81 | } 82 | 83 | type User implements Node { 84 | createdAt: DateTime! 85 | email: String! 86 | firstName: String! 87 | id: ID! 88 | lastName: String! 89 | updatedAt: DateTime! 90 | } 91 | 92 | type UserConnection { 93 | """ 94 | https://facebook.github.io/relay/graphql/connections.htm#sec-Edge-Types 95 | """ 96 | edges: [UserEdge] 97 | 98 | """ 99 | https://facebook.github.io/relay/graphql/connections.htm#sec-undefined.PageInfo 100 | """ 101 | pageInfo: PageInfo! 102 | } 103 | 104 | type UserEdge { 105 | """https://facebook.github.io/relay/graphql/connections.htm#sec-Cursor""" 106 | cursor: String! 107 | 108 | """https://facebook.github.io/relay/graphql/connections.htm#sec-Node""" 109 | node: User! 110 | } 111 | 112 | type Viewer { 113 | allBlogPosts( 114 | """Returns the elements in the list that come after the specified cursor""" 115 | after: String 116 | 117 | """Returns the first n elements from the list.""" 118 | first: Int! 119 | orderBy: BlogPostOrderBy 120 | ): BlogPostConnection! 121 | allUsers( 122 | """Returns the elements in the list that come after the specified cursor""" 123 | after: String 124 | 125 | """Returns the first n elements from the list.""" 126 | first: Int! 127 | ): UserConnection! 128 | BlogPost(id: String!): BlogPost! 129 | User(id: String!): User! 130 | } 131 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/BlogPost.ts: -------------------------------------------------------------------------------- 1 | import { extendType, objectType, stringArg } from "@nexus/schema" 2 | import { buildOrderBy } from "./utils" 3 | import { fromGlobalId } from "graphql-relay" 4 | 5 | export const BlogPost = objectType({ 6 | name: "BlogPost", 7 | definition(t) { 8 | t.implements("Node") 9 | t.model.title() 10 | t.model.content() 11 | t.model.createdAt() 12 | t.model.updatedAt() 13 | }, 14 | }) 15 | 16 | export const AllBlogPostsOrderBy = buildOrderBy("BlogPost", [ 17 | "createdAt", 18 | "updatedAt", 19 | ]) 20 | 21 | export const PostQuery = extendType({ 22 | type: "Viewer", 23 | definition(t) { 24 | t.field("BlogPost", { 25 | type: "BlogPost", 26 | args: { id: stringArg({ required: true }) }, 27 | resolve(_root, args, ctx) { 28 | return ctx.blogPost.findOne(args) 29 | }, 30 | }) 31 | t.connectionField("allBlogPosts", { 32 | type: "BlogPost", 33 | additionalArgs: { 34 | orderBy: "BlogPostOrderBy", 35 | }, 36 | async nodes(_root, args, ctx) { 37 | return ctx.blogPost.findMany(args) 38 | }, 39 | }) 40 | }, 41 | }) 42 | 43 | export const PostMutation = extendType({ 44 | type: "Mutation", 45 | definition(t) { 46 | t.field("createBlogPost", { 47 | type: "BlogPost", 48 | args: { 49 | title: stringArg({ required: true }), 50 | content: stringArg({ required: true }), 51 | }, 52 | resolve(_root, args, ctx) { 53 | return ctx.blogPost.create({ 54 | title: args.title, 55 | content: args.content, 56 | }) 57 | }, 58 | }) 59 | 60 | // t.field("votePost", { 61 | // type: "Post", 62 | // args: { 63 | // id: stringArg({ required: true }), 64 | // }, 65 | // async resolve(_root, args, ctx) { 66 | // return ctx.post.votePost(args) 67 | // }, 68 | // }) 69 | }, 70 | }) 71 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/Meta.ts: -------------------------------------------------------------------------------- 1 | import { enumType } from "@nexus/schema" 2 | 3 | export const OrderByEnum = enumType({ 4 | name: "OrderBy", 5 | members: ["asc", "desc"], 6 | }) 7 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/Node.ts: -------------------------------------------------------------------------------- 1 | import { core, interfaceType, queryField, stringArg } from "@nexus/schema" 2 | import { toGlobalId, fromGlobalId } from "graphql-relay" 3 | 4 | // TypeName:ID -> base64 5 | 6 | export const Node = interfaceType({ 7 | name: "Node", 8 | definition(t) { 9 | t.id("id", (root, _args, _ctx, info) => 10 | toGlobalId(info.parentType.name, root.id), 11 | ) 12 | t.resolveType((root) => { 13 | return (root as any).__typeName 14 | }) 15 | }, 16 | }) 17 | 18 | function assertAllTypesCovered(_x: never, id: string): never { 19 | throw new Error("could not find any resource with id: " + id) 20 | } 21 | 22 | export const NodeField = queryField("node", { 23 | type: "Node", 24 | args: { 25 | id: stringArg({ required: true }), 26 | }, 27 | async resolve(_root, args, ctx) { 28 | const { type } = fromGlobalId(args.id) as { 29 | type: core.GetGen2<"abstractResolveReturn", "Node"> 30 | id: string 31 | } 32 | 33 | if (type === "BlogPost") { 34 | const post = await ctx.blogPost.findOne({ id: args.id }) 35 | 36 | return { ...post, __typeName: type } 37 | } 38 | 39 | if (type === "User") { 40 | const user = await ctx.user.findOne({ id: args.id }) 41 | 42 | return { ...user, __typeName: type } 43 | } 44 | 45 | assertAllTypesCovered(type, args.id) 46 | }, 47 | }) 48 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/User.ts: -------------------------------------------------------------------------------- 1 | import { extendType, objectType, stringArg } from "@nexus/schema" 2 | 3 | export const UserQuerySimple = extendType({ 4 | type: "Viewer", 5 | definition(t) { 6 | t.field("User", { 7 | type: "User", 8 | args: { 9 | id: stringArg({ required: true }), 10 | }, 11 | resolve(_root, args, ctx) { 12 | return ctx.user.findOne(args) 13 | }, 14 | }) 15 | t.connectionField("allUsers", { 16 | type: "User", 17 | nodes(_root, args, ctx) { 18 | return ctx.user.findMany(args) 19 | }, 20 | }) 21 | }, 22 | }) 23 | 24 | export const User = objectType({ 25 | name: "User", 26 | definition(t) { 27 | t.implements("Node") 28 | t.model.firstName() 29 | t.model.lastName() 30 | t.model.email() 31 | t.model.createdAt() 32 | t.model.updatedAt() 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/Viewer.ts: -------------------------------------------------------------------------------- 1 | import { extendType } from "@nexus/schema" 2 | 3 | export const ViewerQuery = extendType({ 4 | type: "Query", 5 | definition(t) { 6 | t.field("viewer", { 7 | type: "Viewer", 8 | resolve() { 9 | return {} 10 | } 11 | }) 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/context.ts: -------------------------------------------------------------------------------- 1 | import { Services } from "../services" 2 | 3 | export interface Context extends Services {} 4 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/definitions.ts: -------------------------------------------------------------------------------- 1 | export * from './BlogPost' 2 | export * from './Meta' 3 | export * from './Node' 4 | export * from './User' 5 | export * from './Viewer' 6 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { makeSchema, connectionPlugin } from "@nexus/schema" 2 | import { nexusSchemaPrisma } from "nexus-plugin-prisma/schema" 3 | import * as path from "path" 4 | import * as typeDefs from "./definitions" 5 | 6 | export const schema = makeSchema({ 7 | types: typeDefs, 8 | outputs: { 9 | typegen: path.join(__dirname, "..", "nexus-typegen.ts"), 10 | schema: path.join(__dirname, "..", "schema.graphql"), 11 | }, 12 | 13 | plugins: [ 14 | connectionPlugin({ 15 | disableBackwardPagination: true, 16 | strictArgs: true, 17 | cursorFromNode(node) { 18 | return node.id 19 | }, 20 | }), 21 | nexusSchemaPrisma({ 22 | outputs: { 23 | typegen: path.join(__dirname, "..", "nexus-prisma-typegen.ts"), 24 | }, 25 | }), 26 | ], 27 | typegenAutoConfig: { 28 | sources: [ 29 | { 30 | source: path.join( 31 | __dirname, 32 | "..", 33 | "node_modules", 34 | ".prisma", 35 | "client", 36 | "index.d.ts", 37 | ), 38 | alias: "prisma", 39 | }, 40 | { 41 | source: require.resolve("./context"), 42 | alias: "ctx", 43 | } 44 | ], 45 | contextType: "ctx.Context", 46 | }, 47 | }) 48 | 49 | export default schema 50 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/schema/utils.ts: -------------------------------------------------------------------------------- 1 | import { core, inputObjectType } from "@nexus/schema" 2 | import { OrderByEnum } from "./Meta" 3 | 4 | export function buildOrderBy>( 5 | model: M, 6 | fields: Array[M]>, 7 | ) { 8 | return inputObjectType({ 9 | name: `${model}OrderBy`, 10 | definition(t) { 11 | for (const f of fields) { 12 | t.field(f as string, { type: OrderByEnum }) 13 | } 14 | }, 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/services/Post.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { PaginationArgs, relayToPrismaPagination } from "./utils" 3 | import { fromGlobalId } from "graphql-relay" 4 | 5 | export class BlogPostService { 6 | constructor(protected prisma: PrismaClient) {} 7 | 8 | async findOne(params: { id: string }) { 9 | const user = await this.prisma.blogPost.findOne({ 10 | where: { id: fromGlobalId(params.id).id }, 11 | }) 12 | 13 | if (!user) { 14 | throw new Error("could not find blog post with id: " + params.id) 15 | } 16 | 17 | return user 18 | } 19 | 20 | async findMany(params: PaginationArgs) { 21 | return this.prisma.blogPost.findMany({ 22 | ...relayToPrismaPagination(params), 23 | }) 24 | } 25 | 26 | async create(params: { title: string; content: string }) { 27 | return this.prisma.blogPost.create({ 28 | data: params, 29 | }) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/services/User.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { fromGlobalId } from "graphql-relay" 3 | import { PaginationArgs, relayToPrismaPagination } from "./utils" 4 | 5 | export class UserService { 6 | constructor(protected prisma: PrismaClient) {} 7 | 8 | async findOne(params: { id: string }) { 9 | const user = await this.prisma.user.findOne({ 10 | where: { id: fromGlobalId(params.id).id }, 11 | }) 12 | 13 | if (!user) { 14 | throw new Error("could not find user with id: " + params.id) 15 | } 16 | 17 | return user 18 | } 19 | 20 | findMany(params: PaginationArgs) { 21 | return this.prisma.user.findMany({ 22 | ...relayToPrismaPagination(params), 23 | }) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/services/index.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from "./User" 2 | import { BlogPostService } from "./Post" 3 | import { PrismaClient } from "@prisma/client" 4 | 5 | export interface Services { 6 | user: UserService, 7 | blogPost: BlogPostService 8 | } 9 | 10 | export function buildServices(prisma: PrismaClient): Services { 11 | return { 12 | user: new UserService(prisma), 13 | blogPost: new BlogPostService(prisma), 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/services/utils.ts: -------------------------------------------------------------------------------- 1 | export declare type PaginationArgs = { 2 | first?: number | null 3 | after?: string | null 4 | last?: number | null 5 | before?: string | null 6 | } 7 | 8 | export function relayToPrismaPagination( 9 | args: PaginationArgs, 10 | ): { cursor?: { id: string }; take?: number; skip?: number } { 11 | const { first, last, before, after } = args 12 | 13 | // If no pagination set, don't touch the args 14 | if (!first && !last && !before && !after) { 15 | return {} 16 | } 17 | 18 | /** 19 | * This is currently only possible with js transformation on the result. eg: 20 | * after: 1, last: 1 21 | * ({ 22 | * cursor: { id: $before }, 23 | * take: Number.MAX_SAFE_INTEGER, 24 | * skip: 1 25 | * }).slice(length - $last, length) 26 | */ 27 | if (after && last) { 28 | throw new Error(`after and last can't be set simultaneously`) 29 | } 30 | 31 | /** 32 | * This is currently only possible with js transformation on the result. eg: 33 | * before: 4, first: 1 34 | * ({ 35 | * cursor: { id: $before }, 36 | * take: Number.MIN_SAFE_INTEGER, 37 | * skip: 1 38 | * }).slice(0, $first) 39 | */ 40 | if (before && first) { 41 | throw new Error(`before and first can't be set simultaneously`) 42 | } 43 | 44 | // Edge-case: simulates a single `before` with a hack 45 | if (before && !first && !last && !after) { 46 | return { 47 | cursor: { id: before }, 48 | skip: 1, 49 | take: Number.MIN_SAFE_INTEGER, 50 | } 51 | } 52 | 53 | const take = resolveTake(first, last) 54 | const cursor = resolveCursor(before, after) 55 | const skip = resolveSkip(cursor) 56 | 57 | const newArgs = { 58 | take, 59 | cursor, 60 | skip, 61 | } 62 | 63 | return newArgs 64 | } 65 | 66 | function resolveTake( 67 | first: number | null | undefined, 68 | last: number | null | undefined, 69 | ): number | undefined { 70 | if (first && last) { 71 | throw new Error(`first and last can't be set simultaneously`) 72 | } 73 | 74 | if (first) { 75 | if (first < 0) { 76 | throw new Error(`first can't be negative`) 77 | } 78 | return first 79 | } 80 | 81 | if (last) { 82 | if (last < 0) { 83 | throw new Error(`last can't be negative`) 84 | } 85 | 86 | if (last === 0) { 87 | return 0 88 | } 89 | 90 | return last * -1 91 | } 92 | 93 | return undefined 94 | } 95 | 96 | function resolveCursor( 97 | before: string | null | undefined, 98 | after: string | null | undefined, 99 | ) { 100 | if (before && after) { 101 | throw new Error(`before and after can't be set simultaneously`) 102 | } 103 | 104 | if (before) { 105 | return { id: before } 106 | } 107 | 108 | if (after) { 109 | return { id: after } 110 | } 111 | 112 | return undefined 113 | } 114 | 115 | function resolveSkip(cursor: { id: string } | null | undefined) { 116 | if (cursor) { 117 | return 1 118 | } 119 | 120 | return undefined 121 | } 122 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-relay/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["esnext"], 6 | "strict": true, 7 | "rootDir": ".", 8 | "sourceMap": true, 9 | "esModuleInterop": true, 10 | "noFallthroughCasesInSwitch": true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/.gitignore: -------------------------------------------------------------------------------- 1 | .vercel 2 | .env -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/README.md: -------------------------------------------------------------------------------- 1 | # graphql-with-prisma-simple 2 | 3 | ### Try It 4 | 5 | ``` 6 | yarn && yarn dev 7 | ``` 8 | 9 | ## Deploy your own 10 | 11 | Deploy the example using [Vercel](https://vercel.com): 12 | 13 | [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/project?template=https://github.com/prisma-labs/nextjs-graphql-api-examples/tree/master/packages/graphql-with-prisma-simple) 14 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/api/index.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { graphqlHTTP } from "express-graphql" 3 | import schema from "../schema" 4 | import { buildServices } from "../services" 5 | 6 | const prisma = new PrismaClient() 7 | 8 | const allowCors = (fn: Function) => async (req: any, res: any) => { 9 | res.setHeader('Access-Control-Allow-Credentials', true) 10 | res.setHeader('Access-Control-Allow-Origin', '*') 11 | // another option 12 | // res.setHeader('Access-Control-Allow-Origin', req.headers.origin); 13 | res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS') 14 | res.setHeader( 15 | 'Access-Control-Allow-Headers', 16 | 'X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version' 17 | ) 18 | if (req.method === 'OPTIONS') { 19 | res.status(200).end() 20 | return 21 | } 22 | return await fn(req, res) 23 | } 24 | 25 | const handler = graphqlHTTP(_ => ({ 26 | schema, 27 | graphiql: true, 28 | pretty: true, 29 | context: { 30 | ...buildServices(prisma), 31 | }, 32 | })) 33 | 34 | export default allowCors(handler) -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/api/serverful.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient } from "@prisma/client" 2 | import { ApolloServer } from "apollo-server" 3 | import schema from "../schema" 4 | import { buildServices } from "../services" 5 | 6 | const prisma = new PrismaClient() 7 | 8 | const server = new ApolloServer({ 9 | schema, 10 | context: () => { 11 | return buildServices(prisma) 12 | }, 13 | }) 14 | 15 | server.listen().then(({ url }) => { 16 | console.log(`🚀 Server ready at ${url}`) 17 | }) 18 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/nexus-prisma-typegen.ts: -------------------------------------------------------------------------------- 1 | import * as Typegen from 'nexus-plugin-prisma/typegen' 2 | import * as Prisma from '@prisma/client'; 3 | 4 | // Pagination type 5 | type Pagination = { 6 | first?: boolean 7 | last?: boolean 8 | before?: boolean 9 | after?: boolean 10 | } 11 | 12 | // Prisma custom scalar names 13 | type CustomScalars = 'DateTime' 14 | 15 | // Prisma model type definitions 16 | interface PrismaModels { 17 | Post: Prisma.Post 18 | User: Prisma.User 19 | } 20 | 21 | // Prisma input types metadata 22 | interface NexusPrismaInputs { 23 | Query: { 24 | posts: { 25 | filtering: 'id' | 'title' | 'createdAt' | 'updatedAt' | 'url' | 'votes' | 'AND' | 'OR' | 'NOT' 26 | ordering: 'id' | 'title' | 'createdAt' | 'updatedAt' | 'url' | 'votes' 27 | } 28 | users: { 29 | filtering: 'id' | 'createdAt' | 'updatedAt' | 'email' | 'firstName' | 'lastName' | 'password' | 'AND' | 'OR' | 'NOT' 30 | ordering: 'id' | 'createdAt' | 'updatedAt' | 'email' | 'firstName' | 'lastName' | 'password' 31 | } 32 | }, 33 | Post: { 34 | 35 | } 36 | User: { 37 | 38 | } 39 | } 40 | 41 | // Prisma output types metadata 42 | interface NexusPrismaOutputs { 43 | Query: { 44 | post: 'Post' 45 | posts: 'Post' 46 | user: 'User' 47 | users: 'User' 48 | }, 49 | Mutation: { 50 | createOnePost: 'Post' 51 | updateOnePost: 'Post' 52 | updateManyPost: 'BatchPayload' 53 | deleteOnePost: 'Post' 54 | deleteManyPost: 'BatchPayload' 55 | upsertOnePost: 'Post' 56 | createOneUser: 'User' 57 | updateOneUser: 'User' 58 | updateManyUser: 'BatchPayload' 59 | deleteOneUser: 'User' 60 | deleteManyUser: 'BatchPayload' 61 | upsertOneUser: 'User' 62 | }, 63 | Post: { 64 | id: 'String' 65 | title: 'String' 66 | createdAt: 'DateTime' 67 | updatedAt: 'DateTime' 68 | url: 'String' 69 | votes: 'Int' 70 | } 71 | User: { 72 | id: 'String' 73 | createdAt: 'DateTime' 74 | updatedAt: 'DateTime' 75 | email: 'String' 76 | firstName: 'String' 77 | lastName: 'String' 78 | password: 'String' 79 | } 80 | } 81 | 82 | // Helper to gather all methods relative to a model 83 | interface NexusPrismaMethods { 84 | Post: Typegen.NexusPrismaFields<'Post'> 85 | User: Typegen.NexusPrismaFields<'User'> 86 | Query: Typegen.NexusPrismaFields<'Query'> 87 | Mutation: Typegen.NexusPrismaFields<'Mutation'> 88 | } 89 | 90 | interface NexusPrismaGenTypes { 91 | inputs: NexusPrismaInputs 92 | outputs: NexusPrismaOutputs 93 | methods: NexusPrismaMethods 94 | models: PrismaModels 95 | pagination: Pagination 96 | scalars: CustomScalars 97 | } 98 | 99 | declare global { 100 | interface NexusPrismaGen extends NexusPrismaGenTypes {} 101 | 102 | type NexusPrisma< 103 | TypeName extends string, 104 | ModelOrCrud extends 'model' | 'crud' 105 | > = Typegen.GetNexusPrisma; 106 | } 107 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/nexus-typegen.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file was generated by Nexus Schema 3 | * Do not make changes to this file directly 4 | */ 5 | 6 | import * as ctx from "./schema/context" 7 | import * as prisma from "./node_modules/.prisma/client/index" 8 | 9 | 10 | 11 | declare global { 12 | interface NexusGenCustomOutputProperties { 13 | model: NexusPrisma 14 | crud: any 15 | } 16 | } 17 | 18 | declare global { 19 | interface NexusGen extends NexusGenTypes {} 20 | } 21 | 22 | export interface NexusGenInputs { 23 | PostOrderBy: { // input type 24 | createdAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 25 | title?: NexusGenEnums['OrderBy'] | null; // OrderBy 26 | updatedAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 27 | votes?: NexusGenEnums['OrderBy'] | null; // OrderBy 28 | } 29 | UserOrderBy: { // input type 30 | createdAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 31 | firstName?: NexusGenEnums['OrderBy'] | null; // OrderBy 32 | lastName?: NexusGenEnums['OrderBy'] | null; // OrderBy 33 | updatedAt?: NexusGenEnums['OrderBy'] | null; // OrderBy 34 | } 35 | } 36 | 37 | export interface NexusGenEnums { 38 | OrderBy: "asc" | "desc" 39 | } 40 | 41 | export interface NexusGenRootTypes { 42 | Mutation: {}; 43 | Post: prisma.Post; 44 | Query: {}; 45 | User: prisma.User; 46 | _QueryMeta: { // root type 47 | count: number; // Int! 48 | } 49 | String: string; 50 | Int: number; 51 | Float: number; 52 | Boolean: boolean; 53 | ID: string; 54 | DateTime: any; 55 | } 56 | 57 | export interface NexusGenAllTypes extends NexusGenRootTypes { 58 | PostOrderBy: NexusGenInputs['PostOrderBy']; 59 | UserOrderBy: NexusGenInputs['UserOrderBy']; 60 | OrderBy: NexusGenEnums['OrderBy']; 61 | } 62 | 63 | export interface NexusGenFieldTypes { 64 | Mutation: { // field return type 65 | createPost: NexusGenRootTypes['Post']; // Post! 66 | votePost: NexusGenRootTypes['Post']; // Post! 67 | } 68 | Post: { // field return type 69 | createdAt: any; // DateTime! 70 | id: string; // String! 71 | title: string; // String! 72 | updatedAt: any; // DateTime! 73 | url: string; // String! 74 | votes: number; // Int! 75 | } 76 | Query: { // field return type 77 | _allPostsMeta: NexusGenRootTypes['_QueryMeta']; // _QueryMeta! 78 | _allUsersMeta: NexusGenRootTypes['_QueryMeta']; // _QueryMeta! 79 | allPosts: NexusGenRootTypes['Post'][]; // [Post!]! 80 | allUsers: NexusGenRootTypes['User'][]; // [User!]! 81 | Post: NexusGenRootTypes['Post']; // Post! 82 | User: NexusGenRootTypes['User']; // User! 83 | } 84 | User: { // field return type 85 | createdAt: any; // DateTime! 86 | email: string; // String! 87 | firstName: string; // String! 88 | id: string; // String! 89 | lastName: string; // String! 90 | updatedAt: any; // DateTime! 91 | } 92 | _QueryMeta: { // field return type 93 | count: number; // Int! 94 | } 95 | } 96 | 97 | export interface NexusGenArgTypes { 98 | Mutation: { 99 | createPost: { // args 100 | title: string; // String! 101 | url: string; // String! 102 | } 103 | votePost: { // args 104 | id: string; // String! 105 | } 106 | } 107 | Query: { 108 | allPosts: { // args 109 | first?: number | null; // Int 110 | orderBy?: NexusGenInputs['PostOrderBy'] | null; // PostOrderBy 111 | skip?: number | null; // Int 112 | } 113 | allUsers: { // args 114 | first?: number | null; // Int 115 | orderBy?: NexusGenInputs['UserOrderBy'] | null; // UserOrderBy 116 | skip?: number | null; // Int 117 | } 118 | Post: { // args 119 | id: string; // String! 120 | } 121 | User: { // args 122 | id: string; // String! 123 | } 124 | } 125 | } 126 | 127 | export interface NexusGenAbstractResolveReturnTypes { 128 | } 129 | 130 | export interface NexusGenInheritedFields {} 131 | 132 | export type NexusGenObjectNames = "Mutation" | "Post" | "Query" | "User" | "_QueryMeta"; 133 | 134 | export type NexusGenInputNames = "PostOrderBy" | "UserOrderBy"; 135 | 136 | export type NexusGenEnumNames = "OrderBy"; 137 | 138 | export type NexusGenInterfaceNames = never; 139 | 140 | export type NexusGenScalarNames = "Boolean" | "DateTime" | "Float" | "ID" | "Int" | "String"; 141 | 142 | export type NexusGenUnionNames = never; 143 | 144 | export interface NexusGenTypes { 145 | context: ctx.Context; 146 | inputTypes: NexusGenInputs; 147 | rootTypes: NexusGenRootTypes; 148 | argTypes: NexusGenArgTypes; 149 | fieldTypes: NexusGenFieldTypes; 150 | allTypes: NexusGenAllTypes; 151 | inheritedFields: NexusGenInheritedFields; 152 | objectNames: NexusGenObjectNames; 153 | inputNames: NexusGenInputNames; 154 | enumNames: NexusGenEnumNames; 155 | interfaceNames: NexusGenInterfaceNames; 156 | scalarNames: NexusGenScalarNames; 157 | unionNames: NexusGenUnionNames; 158 | allInputTypes: NexusGenTypes['inputNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['scalarNames']; 159 | allOutputTypes: NexusGenTypes['objectNames'] | NexusGenTypes['enumNames'] | NexusGenTypes['unionNames'] | NexusGenTypes['interfaceNames'] | NexusGenTypes['scalarNames']; 160 | allNamedTypes: NexusGenTypes['allInputTypes'] | NexusGenTypes['allOutputTypes'] 161 | abstractTypes: NexusGenTypes['interfaceNames'] | NexusGenTypes['unionNames']; 162 | abstractResolveReturn: NexusGenAbstractResolveReturnTypes; 163 | } 164 | 165 | 166 | declare global { 167 | interface NexusGenPluginTypeConfig { 168 | } 169 | interface NexusGenPluginFieldConfig { 170 | } 171 | interface NexusGenPluginSchemaConfig { 172 | } 173 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "zeit-typescript", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "yarn ts-node-dev --transpile-only ./api/serverful.ts", 8 | "vercel:dev": "vercel dev", 9 | "generate:nexus": "ts-node --transpile-only schema", 10 | "now-build": "yarn prisma generate && yarn -s generate:nexus", 11 | "seed": "yarn ts-node prisma/seed.ts", 12 | "postinstall": "yarn prisma generate" 13 | }, 14 | "prettier": { 15 | "semi": false, 16 | "trailingComma": "all" 17 | }, 18 | "dependencies": { 19 | "@devoxa/prisma-relay-cursor-connection": "^1.0.1", 20 | "@nexus/schema": "^0.14.0", 21 | "apollo-server": "^2.16.1", 22 | "express-graphql": "^0.11.0", 23 | "graphql": "^14.5.8", 24 | "nexus-plugin-prisma": "^0.16.1" 25 | }, 26 | "devDependencies": { 27 | "@now/node": "^1.0.1", 28 | "@types/faker": "^4.1.12", 29 | "@types/node": "^12.11.1", 30 | "faker": "^4.1.0", 31 | "ts-node": "^8.4.1", 32 | "ts-node-dev": "^1.0.0-pre.56", 33 | "typescript": "^3.9.7" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729172424-init/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200729172424-init` 2 | 3 | This migration has been generated by Flavian DESVERNE at 7/29/2020, 5:24:24 PM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | PRAGMA foreign_keys=OFF; 10 | 11 | CREATE TABLE "quaint"."Post" ( 12 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"id" TEXT NOT NULL ,"title" TEXT NOT NULL ,"updatedAt" DATE NOT NULL ,"url" TEXT NOT NULL ,"votes" INTEGER NOT NULL , 13 | PRIMARY KEY ("id")) 14 | 15 | CREATE TABLE "quaint"."User" ( 16 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"email" TEXT NOT NULL ,"firstName" TEXT NOT NULL ,"id" TEXT NOT NULL ,"lastName" TEXT NOT NULL ,"password" TEXT NOT NULL ,"updatedAt" DATE NOT NULL , 17 | PRIMARY KEY ("id")) 18 | 19 | PRAGMA "quaint".foreign_key_check; 20 | 21 | PRAGMA foreign_keys=ON; 22 | ``` 23 | 24 | ## Changes 25 | 26 | ```diff 27 | diff --git schema.prisma schema.prisma 28 | migration ..20200729172424-init 29 | --- datamodel.dml 30 | +++ datamodel.dml 31 | @@ -1,0 +1,30 @@ 32 | +// This is your Prisma schema file, 33 | +// learn more about it in the docs: https://pris.ly/d/prisma-schema 34 | + 35 | +datasource db { 36 | + provider = "sqlite" 37 | + url = "***" 38 | +} 39 | + 40 | +generator client { 41 | + provider = "prisma-client-js" 42 | +} 43 | + 44 | +model Post { 45 | + id String @id @default(uuid()) 46 | + title String 47 | + createdAt DateTime @default(now()) 48 | + updatedAt DateTime @updatedAt 49 | + url String 50 | + votes Int 51 | +} 52 | + 53 | +model User { 54 | + id String @id @default(uuid()) 55 | + createdAt DateTime @default(now()) 56 | + updatedAt DateTime @updatedAt 57 | + email String 58 | + firstName String 59 | + lastName String 60 | + password String 61 | +} 62 | ``` 63 | 64 | 65 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729172424-init/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "sqlite" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model Post { 14 | id String @id @default(uuid()) 15 | title String 16 | createdAt DateTime @default(now()) 17 | updatedAt DateTime @updatedAt 18 | url String 19 | votes Int 20 | } 21 | 22 | model User { 23 | id String @id @default(uuid()) 24 | createdAt DateTime @default(now()) 25 | updatedAt DateTime @updatedAt 26 | email String 27 | firstName String 28 | lastName String 29 | password String 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729172424-init/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateSource", 6 | "source": "db" 7 | }, 8 | { 9 | "tag": "CreateArgument", 10 | "location": { 11 | "tag": "Source", 12 | "source": "db" 13 | }, 14 | "argument": "provider", 15 | "value": "\"sqlite\"" 16 | }, 17 | { 18 | "tag": "CreateArgument", 19 | "location": { 20 | "tag": "Source", 21 | "source": "db" 22 | }, 23 | "argument": "url", 24 | "value": "\"***\"" 25 | }, 26 | { 27 | "tag": "CreateModel", 28 | "model": "Post" 29 | }, 30 | { 31 | "tag": "CreateField", 32 | "model": "Post", 33 | "field": "id", 34 | "type": "String", 35 | "arity": "Required" 36 | }, 37 | { 38 | "tag": "CreateDirective", 39 | "location": { 40 | "path": { 41 | "tag": "Field", 42 | "model": "Post", 43 | "field": "id" 44 | }, 45 | "directive": "id" 46 | } 47 | }, 48 | { 49 | "tag": "CreateDirective", 50 | "location": { 51 | "path": { 52 | "tag": "Field", 53 | "model": "Post", 54 | "field": "id" 55 | }, 56 | "directive": "default" 57 | } 58 | }, 59 | { 60 | "tag": "CreateArgument", 61 | "location": { 62 | "tag": "Directive", 63 | "path": { 64 | "tag": "Field", 65 | "model": "Post", 66 | "field": "id" 67 | }, 68 | "directive": "default" 69 | }, 70 | "argument": "", 71 | "value": "uuid()" 72 | }, 73 | { 74 | "tag": "CreateField", 75 | "model": "Post", 76 | "field": "title", 77 | "type": "String", 78 | "arity": "Required" 79 | }, 80 | { 81 | "tag": "CreateField", 82 | "model": "Post", 83 | "field": "createdAt", 84 | "type": "DateTime", 85 | "arity": "Required" 86 | }, 87 | { 88 | "tag": "CreateDirective", 89 | "location": { 90 | "path": { 91 | "tag": "Field", 92 | "model": "Post", 93 | "field": "createdAt" 94 | }, 95 | "directive": "default" 96 | } 97 | }, 98 | { 99 | "tag": "CreateArgument", 100 | "location": { 101 | "tag": "Directive", 102 | "path": { 103 | "tag": "Field", 104 | "model": "Post", 105 | "field": "createdAt" 106 | }, 107 | "directive": "default" 108 | }, 109 | "argument": "", 110 | "value": "now()" 111 | }, 112 | { 113 | "tag": "CreateField", 114 | "model": "Post", 115 | "field": "updatedAt", 116 | "type": "DateTime", 117 | "arity": "Required" 118 | }, 119 | { 120 | "tag": "CreateDirective", 121 | "location": { 122 | "path": { 123 | "tag": "Field", 124 | "model": "Post", 125 | "field": "updatedAt" 126 | }, 127 | "directive": "updatedAt" 128 | } 129 | }, 130 | { 131 | "tag": "CreateField", 132 | "model": "Post", 133 | "field": "url", 134 | "type": "String", 135 | "arity": "Required" 136 | }, 137 | { 138 | "tag": "CreateField", 139 | "model": "Post", 140 | "field": "votes", 141 | "type": "Int", 142 | "arity": "Required" 143 | }, 144 | { 145 | "tag": "CreateModel", 146 | "model": "User" 147 | }, 148 | { 149 | "tag": "CreateField", 150 | "model": "User", 151 | "field": "id", 152 | "type": "String", 153 | "arity": "Required" 154 | }, 155 | { 156 | "tag": "CreateDirective", 157 | "location": { 158 | "path": { 159 | "tag": "Field", 160 | "model": "User", 161 | "field": "id" 162 | }, 163 | "directive": "id" 164 | } 165 | }, 166 | { 167 | "tag": "CreateDirective", 168 | "location": { 169 | "path": { 170 | "tag": "Field", 171 | "model": "User", 172 | "field": "id" 173 | }, 174 | "directive": "default" 175 | } 176 | }, 177 | { 178 | "tag": "CreateArgument", 179 | "location": { 180 | "tag": "Directive", 181 | "path": { 182 | "tag": "Field", 183 | "model": "User", 184 | "field": "id" 185 | }, 186 | "directive": "default" 187 | }, 188 | "argument": "", 189 | "value": "uuid()" 190 | }, 191 | { 192 | "tag": "CreateField", 193 | "model": "User", 194 | "field": "createdAt", 195 | "type": "DateTime", 196 | "arity": "Required" 197 | }, 198 | { 199 | "tag": "CreateDirective", 200 | "location": { 201 | "path": { 202 | "tag": "Field", 203 | "model": "User", 204 | "field": "createdAt" 205 | }, 206 | "directive": "default" 207 | } 208 | }, 209 | { 210 | "tag": "CreateArgument", 211 | "location": { 212 | "tag": "Directive", 213 | "path": { 214 | "tag": "Field", 215 | "model": "User", 216 | "field": "createdAt" 217 | }, 218 | "directive": "default" 219 | }, 220 | "argument": "", 221 | "value": "now()" 222 | }, 223 | { 224 | "tag": "CreateField", 225 | "model": "User", 226 | "field": "updatedAt", 227 | "type": "DateTime", 228 | "arity": "Required" 229 | }, 230 | { 231 | "tag": "CreateDirective", 232 | "location": { 233 | "path": { 234 | "tag": "Field", 235 | "model": "User", 236 | "field": "updatedAt" 237 | }, 238 | "directive": "updatedAt" 239 | } 240 | }, 241 | { 242 | "tag": "CreateField", 243 | "model": "User", 244 | "field": "email", 245 | "type": "String", 246 | "arity": "Required" 247 | }, 248 | { 249 | "tag": "CreateField", 250 | "model": "User", 251 | "field": "firstName", 252 | "type": "String", 253 | "arity": "Required" 254 | }, 255 | { 256 | "tag": "CreateField", 257 | "model": "User", 258 | "field": "lastName", 259 | "type": "String", 260 | "arity": "Required" 261 | }, 262 | { 263 | "tag": "CreateField", 264 | "model": "User", 265 | "field": "password", 266 | "type": "String", 267 | "arity": "Required" 268 | } 269 | ] 270 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729185831-votes-default/README.md: -------------------------------------------------------------------------------- 1 | # Migration `20200729185831-votes-default` 2 | 3 | This migration has been generated by Flavian DESVERNE at 7/29/2020, 6:58:31 PM. 4 | You can check out the [state of the schema](./schema.prisma) after the migration. 5 | 6 | ## Database Steps 7 | 8 | ```sql 9 | PRAGMA foreign_keys=OFF; 10 | 11 | CREATE TABLE "quaint"."new_Post" ( 12 | "createdAt" DATE NOT NULL DEFAULT CURRENT_TIMESTAMP ,"id" TEXT NOT NULL ,"title" TEXT NOT NULL ,"updatedAt" DATE NOT NULL ,"url" TEXT NOT NULL ,"votes" INTEGER NOT NULL DEFAULT 0 , 13 | PRIMARY KEY ("id")) 14 | 15 | INSERT INTO "quaint"."new_Post" ("createdAt", "id", "title", "updatedAt", "url", "votes") SELECT "createdAt", "id", "title", "updatedAt", "url", "votes" FROM "quaint"."Post" 16 | 17 | PRAGMA foreign_keys=off; 18 | DROP TABLE "quaint"."Post";; 19 | PRAGMA foreign_keys=on 20 | 21 | ALTER TABLE "quaint"."new_Post" RENAME TO "Post"; 22 | 23 | PRAGMA "quaint".foreign_key_check; 24 | 25 | PRAGMA foreign_keys=ON; 26 | ``` 27 | 28 | ## Changes 29 | 30 | ```diff 31 | diff --git schema.prisma schema.prisma 32 | migration 20200729172424-init..20200729185831-votes-default 33 | --- datamodel.dml 34 | +++ datamodel.dml 35 | @@ -2,22 +2,23 @@ 36 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 37 | datasource db { 38 | provider = "sqlite" 39 | - url = "***" 40 | + url = "***" 41 | } 42 | generator client { 43 | - provider = "prisma-client-js" 44 | + provider = "prisma-client-js" 45 | + previewFeatures = ["transactionApi"] 46 | } 47 | model Post { 48 | id String @id @default(uuid()) 49 | title String 50 | createdAt DateTime @default(now()) 51 | updatedAt DateTime @updatedAt 52 | url String 53 | - votes Int 54 | + votes Int @default(0) 55 | } 56 | model User { 57 | id String @id @default(uuid()) 58 | ``` 59 | 60 | 61 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729185831-votes-default/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "sqlite" 6 | url = "***" 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | previewFeatures = ["transactionApi"] 12 | } 13 | 14 | model Post { 15 | id String @id @default(uuid()) 16 | title String 17 | createdAt DateTime @default(now()) 18 | updatedAt DateTime @updatedAt 19 | url String 20 | votes Int @default(0) 21 | } 22 | 23 | model User { 24 | id String @id @default(uuid()) 25 | createdAt DateTime @default(now()) 26 | updatedAt DateTime @updatedAt 27 | email String 28 | firstName String 29 | lastName String 30 | password String 31 | } 32 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/20200729185831-votes-default/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.3.14-fixed", 3 | "steps": [ 4 | { 5 | "tag": "CreateDirective", 6 | "location": { 7 | "path": { 8 | "tag": "Field", 9 | "model": "Post", 10 | "field": "votes" 11 | }, 12 | "directive": "default" 13 | } 14 | }, 15 | { 16 | "tag": "CreateArgument", 17 | "location": { 18 | "tag": "Directive", 19 | "path": { 20 | "tag": "Field", 21 | "model": "Post", 22 | "field": "votes" 23 | }, 24 | "directive": "default" 25 | }, 26 | "argument": "", 27 | "value": "0" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/migrations/migrate.lock: -------------------------------------------------------------------------------- 1 | # Prisma Migrate lockfile v1 2 | 3 | 20200729172424-init 4 | 20200729185831-votes-default -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/schema.prisma: -------------------------------------------------------------------------------- 1 | // This is your Prisma schema file, 2 | // learn more about it in the docs: https://pris.ly/d/prisma-schema 3 | 4 | datasource db { 5 | provider = "postgres" 6 | url = env("DATABASE_URL") 7 | } 8 | 9 | generator client { 10 | provider = "prisma-client-js" 11 | } 12 | 13 | model Post { 14 | id String @id @default(uuid()) 15 | title String 16 | createdAt DateTime @default(now()) 17 | updatedAt DateTime @updatedAt 18 | url String 19 | votes Int @default(0) 20 | } 21 | 22 | model User { 23 | id String @id @default(uuid()) 24 | createdAt DateTime @default(now()) 25 | updatedAt DateTime @updatedAt 26 | email String 27 | firstName String 28 | lastName String 29 | password String 30 | } 31 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/prisma/seed.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient, UserCreateInput, PostCreateInput } from "@prisma/client" 2 | import faker from "faker" 3 | 4 | async function seed() { 5 | const prisma = new PrismaClient() 6 | 7 | const userPromises = Array.from(Array(20).keys()).map(_ => { 8 | const firstName = faker.name.firstName() 9 | const lastName = faker.name.lastName() 10 | const user: UserCreateInput = { 11 | firstName, 12 | lastName, 13 | email: faker.internet.exampleEmail(firstName, lastName), 14 | password: faker.random.uuid(), 15 | } 16 | 17 | return prisma.user.create({ 18 | data: user, 19 | }) 20 | }) 21 | 22 | const postPromises = Array.from(Array(30).keys()).map(_ => { 23 | const post: PostCreateInput = { 24 | title: faker.lorem.sentence(), 25 | url: faker.internet.url(), 26 | votes: faker.random.number(100), 27 | } 28 | 29 | return prisma.post.create({ 30 | data: post, 31 | }) 32 | }) 33 | 34 | await Promise.all([...userPromises, ...postPromises] as Promise[]) 35 | 36 | await prisma.disconnect() 37 | 38 | console.log('Seeded db!') 39 | } 40 | 41 | seed() 42 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema.graphql: -------------------------------------------------------------------------------- 1 | ### This file was generated by Nexus Schema 2 | ### Do not make changes to this file directly 3 | 4 | 5 | type _QueryMeta { 6 | count: Int! 7 | } 8 | 9 | scalar DateTime 10 | 11 | type Mutation { 12 | createPost(title: String!, url: String!): Post! 13 | votePost(id: String!): Post! 14 | } 15 | 16 | enum OrderBy { 17 | asc 18 | desc 19 | } 20 | 21 | type Post { 22 | createdAt: DateTime! 23 | id: String! 24 | title: String! 25 | updatedAt: DateTime! 26 | url: String! 27 | votes: Int! 28 | } 29 | 30 | input PostOrderBy { 31 | createdAt: OrderBy 32 | title: OrderBy 33 | updatedAt: OrderBy 34 | votes: OrderBy 35 | } 36 | 37 | type Query { 38 | _allPostsMeta: _QueryMeta! 39 | _allUsersMeta: _QueryMeta! 40 | allPosts(first: Int, orderBy: PostOrderBy, skip: Int): [Post!]! 41 | allUsers(first: Int, orderBy: UserOrderBy, skip: Int): [User!]! 42 | Post(id: String!): Post! 43 | User(id: String!): User! 44 | } 45 | 46 | type User { 47 | createdAt: DateTime! 48 | email: String! 49 | firstName: String! 50 | id: String! 51 | lastName: String! 52 | updatedAt: DateTime! 53 | } 54 | 55 | input UserOrderBy { 56 | createdAt: OrderBy 57 | firstName: OrderBy 58 | lastName: OrderBy 59 | updatedAt: OrderBy 60 | } 61 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/Meta.ts: -------------------------------------------------------------------------------- 1 | import { objectType, enumType } from "@nexus/schema"; 2 | 3 | export const QueryMeta = objectType({ 4 | name: '_QueryMeta', 5 | definition(t) { 6 | t.int('count') 7 | } 8 | }) 9 | 10 | export const OrderByEnum = enumType({ 11 | name: 'OrderBy', 12 | members: ['asc', 'desc'] 13 | }) -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/Post.ts: -------------------------------------------------------------------------------- 1 | import { extendType, objectType, stringArg } from "@nexus/schema" 2 | import { paginationArgs, buildOrderBy } from "./utils" 3 | 4 | export const Post = objectType({ 5 | name: "Post", 6 | definition(t) { 7 | t.model.id() 8 | t.model.createdAt() 9 | t.model.updatedAt() 10 | t.model.title() 11 | t.model.url() 12 | t.model.votes() 13 | }, 14 | }) 15 | 16 | export const PostOrderBy = buildOrderBy("Post", [ 17 | "title", 18 | "createdAt", 19 | "updatedAt", 20 | "votes", 21 | ]) 22 | 23 | export const PostQuery = extendType({ 24 | type: "Query", 25 | definition(t) { 26 | t.list.field("allPosts", { 27 | type: Post, 28 | args: { 29 | ...paginationArgs, 30 | orderBy: PostOrderBy, 31 | }, 32 | resolve(_root, args, ctx) { 33 | return ctx.post.findMany(args) 34 | }, 35 | }) 36 | 37 | t.field("_allPostsMeta", { 38 | type: "_QueryMeta", 39 | async resolve(_root, _args, ctx) { 40 | return { 41 | count: await ctx.post.count(), 42 | } 43 | }, 44 | }) 45 | 46 | t.field("Post", { 47 | type: Post, 48 | args: { 49 | id: stringArg({ required: true }), 50 | }, 51 | resolve(_root, args, ctx) { 52 | return ctx.post.findOne({ id: args.id }) 53 | }, 54 | }) 55 | }, 56 | }) 57 | 58 | export const PostMutation = extendType({ 59 | type: "Mutation", 60 | definition(t) { 61 | t.field("createPost", { 62 | type: Post, 63 | args: { 64 | title: stringArg({ required: true }), 65 | url: stringArg({ required: true }), 66 | }, 67 | resolve(_root, args, ctx) { 68 | return ctx.post.create({ 69 | title: args.title, 70 | url: args.url, 71 | }) 72 | }, 73 | }) 74 | 75 | t.field("votePost", { 76 | type: Post, 77 | args: { 78 | id: stringArg({ required: true }), 79 | }, 80 | async resolve(_root, args, ctx) { 81 | return ctx.post.votePost({ id: args.id }) 82 | }, 83 | }) 84 | }, 85 | }) 86 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/User.ts: -------------------------------------------------------------------------------- 1 | import { extendType, objectType, stringArg } from "@nexus/schema" 2 | import { buildOrderBy, paginationArgs } from "./utils" 3 | 4 | export const User = objectType({ 5 | name: "User", 6 | definition(t) { 7 | t.model.id() 8 | t.model.createdAt() 9 | t.model.updatedAt() 10 | t.model.firstName() 11 | t.model.lastName() 12 | t.model.email() 13 | }, 14 | }) 15 | 16 | export const UserOrderBy = buildOrderBy("User", [ 17 | "firstName", 18 | "lastName", 19 | "createdAt", 20 | "updatedAt", 21 | ]) 22 | 23 | export const UserQuery = extendType({ 24 | type: "Query", 25 | definition(t) { 26 | t.list.field("allUsers", { 27 | type: User, 28 | args: { 29 | ...paginationArgs, 30 | orderBy: UserOrderBy, 31 | }, 32 | resolve(_root, args, ctx) { 33 | return ctx.user.findMany(args) 34 | }, 35 | }) 36 | 37 | t.field("_allUsersMeta", { 38 | type: "_QueryMeta", 39 | async resolve(_root, _args, ctx) { 40 | return { 41 | count: await ctx.user.count(), 42 | } 43 | }, 44 | }) 45 | 46 | t.field("User", { 47 | type: User, 48 | args: { 49 | id: stringArg({ required: true }), 50 | }, 51 | resolve(_root, args, ctx) { 52 | return ctx.user.findOne({ id: args.id }) 53 | }, 54 | }) 55 | }, 56 | }) 57 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/context.ts: -------------------------------------------------------------------------------- 1 | import { UserService, PostService } from "../services"; 2 | 3 | export type Context = { 4 | user: UserService, 5 | post: PostService 6 | } -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/definitions.ts: -------------------------------------------------------------------------------- 1 | export * from './User' 2 | export * from './Post' 3 | export * from './Meta' -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { makeSchema, connectionPlugin } from "@nexus/schema" 2 | import { nexusSchemaPrisma } from "nexus-plugin-prisma/schema" 3 | import * as path from "path" 4 | import * as typeDefs from "./definitions" 5 | 6 | export const schema = makeSchema({ 7 | types: typeDefs, 8 | outputs: { 9 | typegen: path.join(__dirname, "..", "nexus-typegen.ts"), 10 | schema: path.join(__dirname, "..", "schema.graphql"), 11 | }, 12 | 13 | plugins: [ 14 | nexusSchemaPrisma({ 15 | outputs: { 16 | typegen: path.join(__dirname, "..", "nexus-prisma-typegen.ts"), 17 | }, 18 | }), 19 | ], 20 | typegenAutoConfig: { 21 | sources: [ 22 | { 23 | source: path.join( 24 | __dirname, 25 | "..", 26 | "node_modules", 27 | ".prisma", 28 | "client", 29 | "index.d.ts", 30 | ), 31 | alias: "prisma", 32 | }, 33 | { 34 | source: require.resolve("./context"), 35 | alias: "ctx", 36 | }, 37 | ], 38 | contextType: "ctx.Context", 39 | }, 40 | }) 41 | 42 | export default schema 43 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/schema/utils.ts: -------------------------------------------------------------------------------- 1 | import { intArg, inputObjectType, core } from "@nexus/schema" 2 | import { OrderByEnum } from "./Meta" 3 | 4 | export const paginationArgs = { 5 | first: intArg(), 6 | skip: intArg(), 7 | } 8 | 9 | export function buildOrderBy>( 10 | model: M, 11 | fields: Array[M]>, 12 | ) { 13 | return inputObjectType({ 14 | name: `${model}OrderBy`, 15 | definition(t) { 16 | for (const f of fields) { 17 | t.field(f as string, { type: OrderByEnum }) 18 | } 19 | }, 20 | }) 21 | } 22 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/services/Post.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient, FindManyPostArgs } from "@prisma/client" 2 | import { PaginationArgs, offsetToPrismaPagination } from "./utils" 3 | 4 | export class PostService { 5 | constructor(protected prisma: PrismaClient) {} 6 | 7 | async findOne(params: { id: string }) { 8 | const user = await this.prisma.post.findOne({ where: { id: params.id } }) 9 | 10 | if (!user) { 11 | throw new Error("could not find post with id: " + params.id) 12 | } 13 | 14 | return user 15 | } 16 | 17 | async findMany( 18 | params: PaginationArgs & { orderBy?: FindManyPostArgs["orderBy"] | null }, 19 | ) { 20 | return this.prisma.post.findMany({ 21 | ...offsetToPrismaPagination(params), 22 | orderBy: params.orderBy ?? undefined, 23 | }) 24 | } 25 | 26 | async count() { 27 | return this.prisma.post.count() 28 | } 29 | 30 | async create(params: { title: string; url: string }) { 31 | return this.prisma.post.create({ 32 | data: params, 33 | }) 34 | } 35 | 36 | async votePost(params: { id: string }) { 37 | const currentPost = await this.findOne({ id: params.id }) 38 | 39 | const updatePost = await this.prisma.post.update({ 40 | where: { id: params.id }, 41 | data: { votes: currentPost.votes + 1 }, 42 | }) 43 | 44 | return updatePost 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/services/User.ts: -------------------------------------------------------------------------------- 1 | import { PrismaClient, FindManyUserArgs } from "@prisma/client" 2 | import { PaginationArgs, offsetToPrismaPagination } from "./utils" 3 | 4 | export class UserService { 5 | constructor(protected prisma: PrismaClient) {} 6 | 7 | async findOne(params: { id: string }) { 8 | const user = await this.prisma.user.findOne({ where: { id: params.id } }) 9 | 10 | if (!user) { 11 | throw new Error("could not find user with id: " + params.id) 12 | } 13 | 14 | return user 15 | } 16 | 17 | findMany( 18 | params: PaginationArgs & { orderBy?: FindManyUserArgs["orderBy"] | null }, 19 | ) { 20 | return this.prisma.user.findMany({ 21 | ...offsetToPrismaPagination(params), 22 | orderBy: params.orderBy ?? undefined, 23 | }) 24 | } 25 | 26 | async count() { 27 | return this.prisma.post.count() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/services/index.ts: -------------------------------------------------------------------------------- 1 | import { UserService } from "./User" 2 | import { PostService } from "./Post" 3 | import { PrismaClient } from "@prisma/client" 4 | 5 | export { UserService, PostService } 6 | 7 | export function buildServices(prisma: PrismaClient) { 8 | return { 9 | user: new UserService(prisma), 10 | post: new PostService(prisma), 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/services/utils.ts: -------------------------------------------------------------------------------- 1 | export declare type PaginationArgs = { 2 | first?: number | null 3 | last?: number | null 4 | skip?: number | null 5 | } 6 | 7 | export function offsetToPrismaPagination( 8 | args: PaginationArgs, 9 | ): { take?: number; skip?: number } { 10 | const { first, last, skip } = args 11 | 12 | // If no pagination set, don't touch the args 13 | if (!first && !skip) { 14 | return {} 15 | } 16 | 17 | if (first && last) { 18 | throw new Error("first and last cannot be set simultaneously") 19 | } 20 | 21 | const take = resolveTake(first, last) 22 | 23 | const newArgs = { 24 | take, 25 | skip: skip ?? undefined, 26 | } 27 | 28 | return newArgs 29 | } 30 | 31 | function resolveTake( 32 | first: number | null | undefined, 33 | last: number | null | undefined, 34 | ): number | undefined { 35 | if (first && last) { 36 | throw new Error(`first and last can't be set simultaneously`) 37 | } 38 | 39 | if (first) { 40 | if (first < 0) { 41 | throw new Error(`first can't be negative`) 42 | } 43 | return first 44 | } 45 | 46 | if (last) { 47 | if (last < 0) { 48 | throw new Error(`last can't be negative`) 49 | } 50 | 51 | if (last === 0) { 52 | return 0 53 | } 54 | 55 | return last * -1 56 | } 57 | 58 | return undefined 59 | } 60 | 61 | function resolveCursor( 62 | before: string | null | undefined, 63 | after: string | null | undefined, 64 | ) { 65 | if (before && after) { 66 | throw new Error(`before and after can't be set simultaneously`) 67 | } 68 | 69 | if (before) { 70 | return { id: before } 71 | } 72 | 73 | if (after) { 74 | return { id: after } 75 | } 76 | 77 | return undefined 78 | } 79 | 80 | function resolveSkip(cursor: { id: string } | null | undefined) { 81 | if (cursor) { 82 | return 1 83 | } 84 | 85 | return undefined 86 | } 87 | -------------------------------------------------------------------------------- /packages/graphql-with-prisma-simple/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": ["esnext"], 6 | "strict": true, 7 | "rootDir": ".", 8 | "sourceMap": true, 9 | "esModuleInterop": true 10 | } 11 | } 12 | --------------------------------------------------------------------------------