/build/ | |||||
/node_modules/ |
{ | |||||
// Use IntelliSense to learn about possible attributes. | |||||
// Hover to view descriptions of existing attributes. | |||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 | |||||
"version": "0.2.0", | |||||
"configurations": [ | |||||
{ | |||||
"type": "node", | |||||
"request": "launch", | |||||
"name": "Launch Program", | |||||
"program": "${workspaceFolder}/src/index.ts", | |||||
"preLaunchTask": "tsc: build - tsconfig.json", | |||||
"outFiles": ["${workspaceFolder}/build/**/*.js"] | |||||
} | |||||
] | |||||
} |
# Prepation | |||||
``npm init`` | |||||
See More <https://graphql-code-generator.com/docs/getting-started/installation> | |||||
``npm add --save graphql`` | |||||
or | |||||
``yarn add graphql`` | |||||
``npm add --save-dev @graphql-codegen/cli`` | |||||
or | |||||
``yarn add -D @graphql-codegen/cli`` | |||||
``npx graphql-codegen init`` | |||||
``npm add --save-dev @graphql-codegen/typescript`` | |||||
or | |||||
``yarn add -D @graphql-codegen/typescript`` | |||||
# Server | |||||
``npm add apollo-server`` | |||||
``npm install @types/node --save-dev`` | |||||
``` | |||||
npx tsc --init --rootDir src --outDir build \ | |||||
--esModuleInterop --resolveJsonModule --lib es6 \ | |||||
--module commonjs --allowJs true --noImplicitAny true | |||||
``` | |||||
``npm install --save-dev ts-node nodemon rimraf`` | |||||
# UI | |||||
* init new Angular project ``ng new graphql-demo`` |
overwrite: true | |||||
schema: ./src/schema/*.ts | |||||
generates: | |||||
src/generated/graphql.ts: | |||||
plugins: | |||||
- typescript | |||||
- typescript-resolvers | |||||
- typescript-graphql-request | |||||
- typescript-operations | |||||
./graphql.schema.json: | |||||
plugins: | |||||
- "introspection" |
{ | |||||
"watch": ["src"], | |||||
"ext": ".ts,.js", | |||||
"ignore": [], | |||||
"exec": "ts-node ./src/index.ts" | |||||
} |
{ | |||||
"name": "graphql-pizza-demo", | |||||
"version": "1.0.0", | |||||
"description": "", | |||||
"main": "build/index.js", | |||||
"dependencies": { | |||||
"apollo-server": "^2.15.1", | |||||
"graphql": "^15.3.0", | |||||
"typescript": "^3.9.6" | |||||
}, | |||||
"devDependencies": { | |||||
"@graphql-codegen/cli": "1.16.3", | |||||
"@graphql-codegen/introspection": "1.16.3", | |||||
"@graphql-codegen/typescript": "^1.16.3", | |||||
"@graphql-codegen/typescript-graphql-request": "^1.16.3", | |||||
"@graphql-codegen/typescript-operations": "^1.16.3", | |||||
"@graphql-codegen/typescript-resolvers": "1.16.3", | |||||
"@types/node": "^14.0.20", | |||||
"nodemon": "^2.0.4", | |||||
"rimraf": "^3.0.2", | |||||
"ts-node": "^8.10.2" | |||||
}, | |||||
"scripts": { | |||||
"prebuild": "npm run generate", | |||||
"prestart": "npm run generate", | |||||
"test": "echo \"Error: no test specified\" && exit 1", | |||||
"generate": "graphql-codegen --config codegen.yml", | |||||
"start": "nodemon", | |||||
"start:live": "npm run build && node build/index.js", | |||||
"build": "rimraf ./build && tsc" | |||||
}, | |||||
"author": "", | |||||
"license": "ISC" | |||||
} |
import { Pizza } from "../generated/graphql"; | |||||
import { ToppingList } from "./topping-list"; | |||||
import { ToppingResolver } from "../resolver/topping-resolver"; | |||||
export const PizzaList: Pizza[] = [ | |||||
{ | |||||
id: "0", | |||||
name: "Tonno", | |||||
toppings: [ | |||||
ToppingResolver.getByName("Tunna"), | |||||
ToppingResolver.getByName("Onion") | |||||
] | |||||
}, | |||||
{ | |||||
id: "1", | |||||
name: "Salami", | |||||
toppings: [ | |||||
ToppingResolver.getByName("Salami"), | |||||
] | |||||
}, | |||||
{ | |||||
id: "2", | |||||
name: "420", | |||||
toppings: [ | |||||
ToppingResolver.getByName("Pineapple"), | |||||
] | |||||
} | |||||
] | |||||
import { Pizza, Topping } from "../generated/graphql"; | |||||
export const ToppingList: Topping[] = [ | |||||
{ | |||||
id: "1", | |||||
name: "Tunna", | |||||
}, | |||||
{ | |||||
id: "2", | |||||
name: "Onion", | |||||
}, | |||||
{ | |||||
id: "3", | |||||
name: "Pineapple", | |||||
}, | |||||
{ | |||||
id: "4", | |||||
name: "Salami", | |||||
}, | |||||
{ | |||||
id: "5", | |||||
name: "fish sticks", | |||||
} | |||||
] | |||||
import { GraphQLResolveInfo } from 'graphql'; | |||||
import { GraphQLClient } from 'graphql-request'; | |||||
import { print } from 'graphql'; | |||||
import gql from 'graphql-tag'; | |||||
export type Maybe<T> = T | null; | |||||
export type Exact<T extends { [key: string]: any }> = { [K in keyof T]: T[K] }; | |||||
export type RequireFields<T, K extends keyof T> = { [X in Exclude<keyof T, K>]?: T[X] } & { [P in K]-?: NonNullable<T[P]> }; | |||||
/** All built-in and custom scalars, mapped to their actual values */ | |||||
export type Scalars = { | |||||
ID: string; | |||||
String: string; | |||||
Boolean: boolean; | |||||
Int: number; | |||||
Float: number; | |||||
}; | |||||
export type Pizza = { | |||||
__typename?: 'Pizza'; | |||||
id: Scalars['ID']; | |||||
name: Scalars['String']; | |||||
toppings: Array<Topping>; | |||||
}; | |||||
export type Topping = { | |||||
__typename?: 'Topping'; | |||||
id: Scalars['ID']; | |||||
name: Scalars['String']; | |||||
}; | |||||
export type Query = { | |||||
__typename?: 'Query'; | |||||
getPizzaById: Pizza; | |||||
getToppingById: Topping; | |||||
getPizzaByName: Pizza; | |||||
getToppingByName: Topping; | |||||
listPizza: Array<Maybe<Pizza>>; | |||||
listTopping: Array<Maybe<Topping>>; | |||||
}; | |||||
export type QueryGetPizzaByIdArgs = { | |||||
pizzaId: Scalars['ID']; | |||||
}; | |||||
export type QueryGetToppingByIdArgs = { | |||||
toppingId: Scalars['ID']; | |||||
}; | |||||
export type QueryGetPizzaByNameArgs = { | |||||
pizzaName: Scalars['ID']; | |||||
}; | |||||
export type QueryGetToppingByNameArgs = { | |||||
toppingName: Scalars['ID']; | |||||
}; | |||||
export type ResolverTypeWrapper<T> = Promise<T> | T; | |||||
export type LegacyStitchingResolver<TResult, TParent, TContext, TArgs> = { | |||||
fragment: string; | |||||
resolve: ResolverFn<TResult, TParent, TContext, TArgs>; | |||||
}; | |||||
export type NewStitchingResolver<TResult, TParent, TContext, TArgs> = { | |||||
selectionSet: string; | |||||
resolve: ResolverFn<TResult, TParent, TContext, TArgs>; | |||||
}; | |||||
export type StitchingResolver<TResult, TParent, TContext, TArgs> = LegacyStitchingResolver<TResult, TParent, TContext, TArgs> | NewStitchingResolver<TResult, TParent, TContext, TArgs>; | |||||
export type Resolver<TResult, TParent = {}, TContext = {}, TArgs = {}> = | |||||
| ResolverFn<TResult, TParent, TContext, TArgs> | |||||
| StitchingResolver<TResult, TParent, TContext, TArgs>; | |||||
export type ResolverFn<TResult, TParent, TContext, TArgs> = ( | |||||
parent: TParent, | |||||
args: TArgs, | |||||
context: TContext, | |||||
info: GraphQLResolveInfo | |||||
) => Promise<TResult> | TResult; | |||||
export type SubscriptionSubscribeFn<TResult, TParent, TContext, TArgs> = ( | |||||
parent: TParent, | |||||
args: TArgs, | |||||
context: TContext, | |||||
info: GraphQLResolveInfo | |||||
) => AsyncIterator<TResult> | Promise<AsyncIterator<TResult>>; | |||||
export type SubscriptionResolveFn<TResult, TParent, TContext, TArgs> = ( | |||||
parent: TParent, | |||||
args: TArgs, | |||||
context: TContext, | |||||
info: GraphQLResolveInfo | |||||
) => TResult | Promise<TResult>; | |||||
export interface SubscriptionSubscriberObject<TResult, TKey extends string, TParent, TContext, TArgs> { | |||||
subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; | |||||
resolve?: SubscriptionResolveFn<TResult, { [key in TKey]: TResult }, TContext, TArgs>; | |||||
} | |||||
export interface SubscriptionResolverObject<TResult, TParent, TContext, TArgs> { | |||||
subscribe: SubscriptionSubscribeFn<any, TParent, TContext, TArgs>; | |||||
resolve: SubscriptionResolveFn<TResult, any, TContext, TArgs>; | |||||
} | |||||
export type SubscriptionObject<TResult, TKey extends string, TParent, TContext, TArgs> = | |||||
| SubscriptionSubscriberObject<TResult, TKey, TParent, TContext, TArgs> | |||||
| SubscriptionResolverObject<TResult, TParent, TContext, TArgs>; | |||||
export type SubscriptionResolver<TResult, TKey extends string, TParent = {}, TContext = {}, TArgs = {}> = | |||||
| ((...args: any[]) => SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>) | |||||
| SubscriptionObject<TResult, TKey, TParent, TContext, TArgs>; | |||||
export type TypeResolveFn<TTypes, TParent = {}, TContext = {}> = ( | |||||
parent: TParent, | |||||
context: TContext, | |||||
info: GraphQLResolveInfo | |||||
) => Maybe<TTypes> | Promise<Maybe<TTypes>>; | |||||
export type IsTypeOfResolverFn<T = {}> = (obj: T, info: GraphQLResolveInfo) => boolean | Promise<boolean>; | |||||
export type NextResolverFn<T> = () => Promise<T>; | |||||
export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs = {}> = ( | |||||
next: NextResolverFn<TResult>, | |||||
parent: TParent, | |||||
args: TArgs, | |||||
context: TContext, | |||||
info: GraphQLResolveInfo | |||||
) => TResult | Promise<TResult>; | |||||
/** Mapping between all available schema types and the resolvers types */ | |||||
export type ResolversTypes = { | |||||
Pizza: ResolverTypeWrapper<Pizza>; | |||||
ID: ResolverTypeWrapper<Scalars['ID']>; | |||||
String: ResolverTypeWrapper<Scalars['String']>; | |||||
Topping: ResolverTypeWrapper<Topping>; | |||||
Query: ResolverTypeWrapper<{}>; | |||||
Boolean: ResolverTypeWrapper<Scalars['Boolean']>; | |||||
}; | |||||
/** Mapping between all available schema types and the resolvers parents */ | |||||
export type ResolversParentTypes = { | |||||
Pizza: Pizza; | |||||
ID: Scalars['ID']; | |||||
String: Scalars['String']; | |||||
Topping: Topping; | |||||
Query: {}; | |||||
Boolean: Scalars['Boolean']; | |||||
}; | |||||
export type PizzaResolvers<ContextType = any, ParentType extends ResolversParentTypes['Pizza'] = ResolversParentTypes['Pizza']> = { | |||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>; | |||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>; | |||||
toppings?: Resolver<Array<ResolversTypes['Topping']>, ParentType, ContextType>; | |||||
__isTypeOf?: IsTypeOfResolverFn<ParentType>; | |||||
}; | |||||
export type ToppingResolvers<ContextType = any, ParentType extends ResolversParentTypes['Topping'] = ResolversParentTypes['Topping']> = { | |||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>; | |||||
name?: Resolver<ResolversTypes['String'], ParentType, ContextType>; | |||||
__isTypeOf?: IsTypeOfResolverFn<ParentType>; | |||||
}; | |||||
export type QueryResolvers<ContextType = any, ParentType extends ResolversParentTypes['Query'] = ResolversParentTypes['Query']> = { | |||||
getPizzaById?: Resolver<ResolversTypes['Pizza'], ParentType, ContextType, RequireFields<QueryGetPizzaByIdArgs, 'pizzaId'>>; | |||||
getToppingById?: Resolver<ResolversTypes['Topping'], ParentType, ContextType, RequireFields<QueryGetToppingByIdArgs, 'toppingId'>>; | |||||
getPizzaByName?: Resolver<ResolversTypes['Pizza'], ParentType, ContextType, RequireFields<QueryGetPizzaByNameArgs, 'pizzaName'>>; | |||||
getToppingByName?: Resolver<ResolversTypes['Topping'], ParentType, ContextType, RequireFields<QueryGetToppingByNameArgs, 'toppingName'>>; | |||||
listPizza?: Resolver<Array<Maybe<ResolversTypes['Pizza']>>, ParentType, ContextType>; | |||||
listTopping?: Resolver<Array<Maybe<ResolversTypes['Topping']>>, ParentType, ContextType>; | |||||
}; | |||||
export type Resolvers<ContextType = any> = { | |||||
Pizza?: PizzaResolvers<ContextType>; | |||||
Topping?: ToppingResolvers<ContextType>; | |||||
Query?: QueryResolvers<ContextType>; | |||||
}; | |||||
/** | |||||
* @deprecated | |||||
* Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. | |||||
*/ | |||||
export type IResolvers<ContextType = any> = Resolvers<ContextType>; | |||||
export type SdkFunctionWrapper = <T>(action: () => Promise<T>) => Promise<T>; | |||||
const defaultWrapper: SdkFunctionWrapper = sdkFunction => sdkFunction(); | |||||
export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = defaultWrapper) { | |||||
return { | |||||
}; | |||||
} | |||||
export type Sdk = ReturnType<typeof getSdk>; |
import { ApolloServer } from "apollo-server"; | |||||
import { Resolvers } from "./generated/graphql"; | |||||
import { pizzaSchema } from "./schema/pizza"; | |||||
import { PizzaResolver } from "./resolver/pizza-resolver"; | |||||
import { ToppingResolver } from "./resolver/topping-resolver"; | |||||
const resolvers: Resolvers = { | |||||
Topping: { | |||||
id: (root, args, context) => { | |||||
return root.id | |||||
}, | |||||
name: (root, args, context) => { | |||||
return root.name | |||||
}, | |||||
}, | |||||
Pizza: { | |||||
id: (root, args, context) => { | |||||
return root.id | |||||
}, | |||||
name: (root, args, context) => { | |||||
return root.name | |||||
}, | |||||
toppings: (root, args, context) => { | |||||
return root.toppings | |||||
}, | |||||
}, | |||||
Query: { | |||||
getPizzaById: (root, args, context) => { | |||||
return PizzaResolver.getById(args.pizzaId); | |||||
}, | |||||
getPizzaByName: (root, args, context) => { | |||||
return PizzaResolver.getByName(args.pizzaName); | |||||
}, | |||||
listPizza: (root, args, context) => { | |||||
return PizzaResolver.list(); | |||||
}, | |||||
getToppingById: (root, args, context) => { | |||||
return ToppingResolver.getById(args.toppingId); | |||||
}, | |||||
getToppingByName: (root, args, context) => { | |||||
return ToppingResolver.getByName(args.toppingName); | |||||
}, | |||||
listTopping: (root, args, context) => { | |||||
return ToppingResolver.list(); | |||||
}, | |||||
} | |||||
} | |||||
const server = new ApolloServer({ | |||||
typeDefs: pizzaSchema, | |||||
resolvers: resolvers as any | |||||
}); | |||||
server.listen().then(({ url }) => { | |||||
console.log(`🚀 Server ready at ${url}`) | |||||
}); |
import { Topping, Pizza, } from "../generated/graphql"; | |||||
import { PizzaList } from "../data/pizza-list"; | |||||
export class PizzaResolver { | |||||
static getById = (id: string): Pizza => { | |||||
return PizzaList.filter(pizza => pizza.id === id)[0]; | |||||
}; | |||||
static list = (): Pizza[] => { | |||||
return PizzaList; | |||||
}; | |||||
static getByName = (name: string): Pizza => { | |||||
return PizzaList.filter(pizza => pizza.name === name)[0]; | |||||
}; | |||||
} |
import { Topping, } from "../generated/graphql"; | |||||
import { ToppingList } from "../data/topping-list"; | |||||
export class ToppingResolver { | |||||
static getById = (id: string): Topping => { | |||||
return ToppingList.filter(topping => topping.id === id)[0]; | |||||
}; | |||||
static getByName = (name: string): Topping => { | |||||
return ToppingList.filter(topping => topping.name === name)[0]; | |||||
}; | |||||
static list = (): Topping[] => { | |||||
return ToppingList; | |||||
}; | |||||
} |
import gql from "graphql-tag"; | |||||
export const pizzaSchema = gql` | |||||
type Pizza { | |||||
id: ID! | |||||
name: String! | |||||
toppings: [Topping!]! | |||||
} | |||||
type Topping { | |||||
id: ID! | |||||
name: String! | |||||
} | |||||
type Query { | |||||
getPizzaById(pizzaId: ID!): Pizza! | |||||
getToppingById(toppingId: ID!): Topping! | |||||
getPizzaByName(pizzaName: ID!): Pizza! | |||||
getToppingByName(toppingName: ID!): Topping! | |||||
listPizza: [Pizza]! | |||||
listTopping: [Topping]! | |||||
} | |||||
`; |
{ | |||||
"compilerOptions": { | |||||
"target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */, | |||||
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, | |||||
"lib": [ | |||||
"es6" | |||||
] /* Specify library files to be included in the compilation. */, | |||||
"allowJs": true /* Allow javascript files to be compiled. */, | |||||
"outDir": "build" /* Redirect output structure to the directory. */, | |||||
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */, | |||||
"strict": true /* Enable all strict type-checking options. */, | |||||
"skipLibCheck": true, | |||||
"noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, | |||||
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, | |||||
"resolveJsonModule": true /* Include modules imported with '.json' extension */, | |||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, | |||||
"sourceMap": true, | |||||
} | |||||
} |