├── .editorconfig ├── .gitignore ├── .huskyrc.json ├── .prettierrc ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── apollo.config.js ├── docs ├── each-client.png └── introduce.png ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── src ├── 01-architecture │ ├── @types │ │ └── index.ts │ ├── connectors │ │ ├── LowDBConnectorImpl.ts │ │ └── connector.ts │ ├── datasources │ │ ├── DataSourceImpl.ts │ │ └── datasource.ts │ ├── main.ts │ ├── models │ │ ├── Address │ │ │ └── AddressEntity.ts │ │ ├── User │ │ │ └── UserEntity.ts │ │ └── model.ts │ ├── readme.md │ ├── repositories │ │ ├── User │ │ │ ├── UserRepositoryImpl.ts │ │ │ └── UserRepsitory.ts │ │ └── repository.ts │ ├── resolvers.ts │ ├── server.ts │ └── typeDefs.ts ├── 02-architecture │ ├── config.ts │ ├── connectors │ │ ├── connector.ts │ │ ├── index.ts │ │ └── postgresql.connector.ts │ ├── credentials.ts │ ├── datasources │ │ ├── index.ts │ │ └── postgresql.datasource.ts │ ├── db │ │ ├── knexfile.ts │ │ ├── migrations │ │ │ └── 20190518163408_db.ts │ │ ├── models │ │ │ ├── User.ts │ │ │ └── index.ts │ │ └── seeds │ │ │ └── db.ts │ ├── directives │ │ └── camelizeKeys │ │ │ ├── camelizeKeys.directive.ts │ │ │ ├── index.ts │ │ │ └── typeDefs.ts │ ├── docker-compose.yaml │ ├── docs │ │ ├── google-cloud-stackdriver-trace-integrate-graphql.png │ │ ├── knex-post-process-response.png │ │ ├── trace-with-dataloader.png │ │ └── trace-without-dataloader.png │ ├── main.ts │ ├── middleware │ │ ├── authenticate.middleware.ts │ │ ├── fields.middleware.ts │ │ ├── index.ts │ │ ├── log.middleware.ts │ │ └── trace.middleware.ts │ ├── modules │ │ ├── address │ │ │ ├── AddressDataSource.ts │ │ │ ├── AddressDataSourceImpl.ts │ │ │ ├── index.ts │ │ │ ├── middlewareTypeMap.ts │ │ │ ├── resolvers.ts │ │ │ └── typeDefs.ts │ │ ├── common │ │ │ ├── index.ts │ │ │ └── typeDefs.ts │ │ ├── post │ │ │ ├── PostDataSource.ts │ │ │ ├── PostDataSourceImpl.ts │ │ │ ├── index.ts │ │ │ ├── resolvers.ts │ │ │ └── typeDefs.ts │ │ └── user │ │ │ ├── UserDataSource.ts │ │ │ ├── UserDataSourceImpl.ts │ │ │ ├── index.ts │ │ │ ├── middlewareTypeMap.ts │ │ │ ├── resolvers.ts │ │ │ └── typeDefs.ts │ ├── package.json │ ├── readme.md │ ├── schema.ts │ ├── server.ts │ ├── traceAgent.ts │ ├── tsconfig.json │ └── util.ts ├── apollo-server-plugins │ ├── apollo-server-plugins-http-header.ts │ ├── index.html │ └── index.ts ├── apply-graphql-middleware-multiple-times │ ├── issue │ │ ├── middleware.ts │ │ ├── schema │ │ │ ├── index.ts │ │ │ ├── schema1.ts │ │ │ └── schema2.ts │ │ └── server.ts │ └── solution │ │ ├── middleware.ts │ │ ├── schema │ │ ├── index.ts │ │ ├── schema1.ts │ │ └── schema2.ts │ │ └── server.ts ├── caching │ ├── 01 │ │ ├── index.html │ │ └── server.ts │ ├── 02 │ │ ├── graphql-server.ts │ │ └── index.html │ ├── 03 │ │ ├── _mixins.js │ │ ├── _mixins.test.js │ │ ├── db.json │ │ ├── lowdb-cache-dev.json │ │ ├── lowdb-cache-test.json │ │ ├── lowdb-cache.test.ts │ │ ├── lowdb-cache.ts │ │ └── server.ts │ └── 04-session-and-cookie │ │ ├── index.html │ │ └── server.ts ├── client-batching-query │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── index.integration.spec.ts.snap │ │ ├── index.integration.spec.ts │ │ ├── index.load.spec.md │ │ └── mockedDB.ts │ ├── db │ │ └── memoryDB.ts │ ├── main.ts │ ├── resolver.ts │ ├── server.ts │ └── typeDefs.ts ├── custom-http-status-code │ ├── expressApollo.ts │ └── server.ts ├── custom-scalar-and-enum │ ├── __tests__ │ │ └── index.integration.spec.ts │ ├── appContext.ts │ ├── db.ts │ ├── resolvers.ts │ ├── schema.ts │ ├── server.ts │ └── typeDefs.ts ├── dataloader │ ├── __tests__ │ │ ├── example.benchmark.ts │ │ └── modules │ │ │ └── user │ │ │ └── userLoader.spec.ts │ ├── db.ts │ ├── main.ts │ ├── modules │ │ └── user │ │ │ ├── index.ts │ │ │ ├── user.ts │ │ │ └── userLoader.ts │ ├── resolvers.ts │ ├── server.ts │ └── typeDefs.ts ├── extend-remote-schema │ ├── service-1 │ │ ├── db.ts │ │ ├── main.ts │ │ ├── resolver.ts │ │ └── typeDefs.ts │ └── service-2 │ │ ├── main.ts │ │ ├── server.ts │ │ └── typeDefs.ts ├── federation │ ├── common │ │ ├── db.ts │ │ └── index.ts │ ├── gateway │ │ └── server.ts │ ├── productService │ │ └── server.ts │ ├── reviewService │ │ └── server.ts │ └── userService │ │ └── server.ts ├── fields-conversion-with-knex │ ├── README.md │ ├── config │ │ └── env.ts │ ├── db │ │ ├── knex.ts │ │ ├── knexfile.ts │ │ ├── migrate.sh │ │ ├── migrations │ │ │ └── 20190801115208_initialize_db.ts │ │ ├── rollback.sh │ │ ├── seed.sh │ │ └── seeds │ │ │ └── initialize_db.ts │ ├── docs │ │ └── README.md │ ├── interfaces │ │ ├── apollo-server │ │ │ └── context.ts │ │ ├── common │ │ │ └── types.ts │ │ └── modules │ │ │ ├── post │ │ │ └── postInput.ts │ │ │ └── tag │ │ │ └── tag.ts │ ├── main.ts │ ├── modules │ │ ├── Post.ts │ │ ├── User.ts │ │ └── index.ts │ ├── resolvers.ts │ ├── server.ts │ ├── typeDefs.ts │ └── utils.ts ├── file-upload │ └── index.ts ├── graphql-authentication-and-authorization │ ├── __tests__ │ │ ├── authWithMiddleware │ │ │ ├── __snapshots__ │ │ │ │ └── index.integration.spec.ts.snap │ │ │ └── index.integration.spec.ts │ │ ├── contexts.ts │ │ ├── fragments.ts │ │ ├── middleware │ │ │ └── auth.spec.ts │ │ ├── mutations.ts │ │ ├── oop │ │ │ └── PostController.spec.ts │ │ └── queries.ts │ ├── db.ts │ ├── directive │ │ ├── auth.ts │ │ └── index.ts │ ├── docs │ │ └── 在GraphQL中实现用户认证和授权的五种方式.md │ ├── fp │ │ ├── auth.ts │ │ ├── combineResolvers.ts │ │ └── index.ts │ ├── main.ts │ ├── middleware │ │ ├── auth.ts │ │ └── index.ts │ ├── oop │ │ ├── ConfigController.ts │ │ ├── PostController.ts │ │ ├── UserController.ts │ │ ├── decorator │ │ │ ├── auth.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── resolvers.ts │ ├── resolversWIthCombineResolvers.ts │ ├── resolversWithClass.ts │ ├── schemaWIthMiddleware.ts │ ├── schemaWithClass.ts │ ├── schemaWithCombineResolvers.ts │ ├── schemaWithDirective.ts │ ├── server.ts │ ├── typeDefs.ts │ └── typeDefsWithDirective.ts ├── handle-url-query-parameter │ └── server.ts ├── initialize-with-modules │ ├── modules │ │ ├── index.ts │ │ ├── post │ │ │ ├── index.ts │ │ │ ├── resolvers.ts │ │ │ └── typeDefs.ts │ │ └── tag │ │ │ ├── index.ts │ │ │ ├── resolvers.ts │ │ │ └── typeDefs.ts │ └── server.ts ├── introspection-query-issue │ └── server.ts ├── merge-constuctor-types-and-string-types │ ├── context.ts │ ├── db.ts │ ├── modules │ │ ├── post │ │ │ └── schema.ts │ │ └── user │ │ │ ├── resolvers.ts │ │ │ ├── schema.ts │ │ │ └── typeDefs.ts │ ├── schema.ts │ └── server.ts ├── mocking │ ├── 01-default-mock │ │ └── server.ts │ └── 02-customizing-mocks │ │ └── server.ts ├── playground-offline │ └── server.ts ├── plugin-and-extensions │ ├── server.ts │ └── stackdiverExtension.ts ├── resolve-fields-in-one-resolver │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── resolvers.spec.ts.snap │ │ └── resolvers.spec.ts │ ├── db │ │ └── memoryDB.ts │ ├── readme.md │ ├── resolvers-better.ts │ ├── resolvers.ts │ ├── server.ts │ ├── typeDefs.ts │ └── types.ts ├── rest-api-caching │ ├── graphql-server.ts │ ├── index.html │ ├── rest-api-server.test.ts │ └── rest-api-server.ts ├── rest-link │ ├── client.ts │ └── rest-api-server.ts ├── retry-link │ ├── client.ts │ └── server.ts ├── schema-delegation │ ├── 01-basic │ │ ├── bookService │ │ │ └── server.ts │ │ └── userService │ │ │ └── server.ts │ ├── 02-delegation-perf-issue │ │ ├── bookService │ │ │ └── server.ts │ │ └── userService │ │ │ └── server.ts │ ├── 03-merge-multiple-schemas-perf-issue │ │ ├── gateway.ts │ │ ├── schema.test.ts │ │ ├── schema.ts │ │ └── util.ts │ └── benchmark.ts ├── schema-stitching-issue │ ├── gateway.ts │ ├── service-a.ts │ └── service-b.ts ├── schema-wrapping │ └── wrapQuery.test.ts ├── serialization-and-deserialization │ ├── gateway.ts │ ├── loadtest.ts │ └── userService.ts ├── set-response-header │ └── server.ts ├── stackoverflow │ ├── 40387508 │ │ └── server.ts │ ├── 57243105 │ │ └── server.ts │ ├── 57802229 │ │ ├── server.test.ts │ │ └── server.ts │ ├── 57809293 │ │ └── index.ts │ ├── 57924628 │ │ ├── index.js │ │ └── models │ │ │ └── user.js │ ├── 58226940 │ │ ├── server.spec.ts │ │ └── server.ts │ ├── 58586576 │ │ ├── hapiApollo.ts │ │ └── server.ts │ ├── 58611959 │ │ ├── constants.js │ │ ├── resolvers │ │ │ ├── index.js │ │ │ ├── task.js │ │ │ └── user.js │ │ ├── server.js │ │ └── typeDefs │ │ │ ├── index.js │ │ │ ├── task.js │ │ │ └── user.js │ ├── 59631413 │ │ └── someDatasource.ts │ ├── 59654437 │ │ └── server.ts │ ├── 59677639 │ │ └── server.ts │ ├── 59829676 │ │ ├── getAssetIdFromService.test.ts │ │ ├── getAssetIdFromService.ts │ │ └── setup.ts │ ├── 60321422 │ │ ├── index.js │ │ └── index.test.js │ ├── 60344588 │ │ └── server.ts │ ├── 60938280 │ │ └── server.ts │ ├── 60977550 │ │ ├── MyCache.ts │ │ └── index.ts │ ├── 61183199 │ │ ├── index.html │ │ └── index.ts │ ├── 61339683 │ │ └── server.ts │ ├── 61425326 │ │ ├── MoviesAPI.ts │ │ ├── SongsAPI.ts │ │ └── server.ts │ ├── 61601783 │ │ └── server.ts │ ├── 62122142 │ │ ├── schema.ts │ │ ├── server.test.ts │ │ └── server.ts │ ├── 62916075 │ │ ├── greet.graphql │ │ ├── resolver.js │ │ └── server.js │ ├── 62950486 │ │ └── server.ts │ ├── 63181608 │ │ ├── datasource.ts │ │ ├── server.ts │ │ └── uploadServer.ts │ ├── 63889126 │ │ ├── app.ts │ │ ├── resolvers │ │ │ ├── bookResolver.ts │ │ │ ├── index.ts │ │ │ └── userResolver.ts │ │ └── typedefs │ │ │ ├── bookSchema.ts │ │ │ ├── index.ts │ │ │ └── userSchema.ts │ ├── 64168556 │ │ └── server.ts │ ├── 64557654 │ │ └── server.js │ ├── 66035127 │ │ └── app.ts │ ├── 66872082 │ │ ├── queries.ts │ │ └── server.ts │ ├── 67830070 │ │ └── app.ts │ ├── 68629868 │ │ ├── errorLink.test.ts │ │ └── errorLink.ts │ ├── 69600434 │ │ └── index.js │ └── 58743670-todo │ │ ├── index.html │ │ └── server.ts ├── subscriptions │ ├── auth.ts │ ├── config.ts │ ├── connectors │ │ ├── Base.ts │ │ ├── Location.ts │ │ ├── Template.ts │ │ ├── User.ts │ │ └── index.ts │ ├── credentials.ts │ ├── datasources │ │ └── memoryDB.ts │ ├── docs │ │ ├── GraphQL Subscription多用户订阅与通知(一).md │ │ └── GraphQL Subscription多用户订阅与通知(二).md │ ├── ecosystem.config.js │ ├── env.ts │ ├── main.ts │ ├── models │ │ ├── Base.ts │ │ └── CommonResponse.ts │ ├── pubsub.ts │ ├── readme.md │ ├── resolvers.ts │ ├── server.ts │ ├── typeDefs.ts │ └── util.ts ├── upload │ ├── __tests__ │ │ ├── __snapshots__ │ │ │ └── server.integration.spec.ts.snap │ │ ├── mutations.ts │ │ └── server.integration.spec.ts │ ├── main.ts │ ├── resolvers.ts │ ├── server.ts │ ├── storage │ │ ├── .DS_Store │ │ ├── a.txt │ │ └── b.txt │ └── typeDefs.ts └── util.ts ├── tsconfig.json └── tslint.json /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = false 9 | insert_final_newline = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env* 3 | .gcp 4 | build 5 | dist 6 | coverage 7 | .DS_Store 8 | uploads -------------------------------------------------------------------------------- /.huskyrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "hooks": { 3 | "pre-commit": "npm run lint && npm run prettify && npm t -- -o" 4 | } 5 | } -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "arrowParens": "always" 6 | } 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10.16.0" 4 | before_install: 5 | # install yarn 6 | - curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.17.3 7 | - export PATH="$HOME/.yarn/bin:$PATH" 8 | install: 9 | - yarn install 10 | jobs: 11 | include: 12 | - stage: "Tests" 13 | name: "Unit Tests with coverage" 14 | script: yarn coverage 15 | - stage: "Compile" 16 | script: yarn build 17 | stages: 18 | - compile 19 | - test 20 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Current TS File", 9 | "type": "node", 10 | "request": "launch", 11 | "args": ["${relativeFile}"], 12 | "runtimeArgs": ["--nolazy", "-r", "ts-node/register"], 13 | "sourceMaps": true, 14 | "cwd": "${workspaceRoot}", 15 | "protocol": "inspector", 16 | "console": "integratedTerminal" 17 | }, 18 | { 19 | "type": "node", 20 | "request": "launch", 21 | "name": "Jest Current File", 22 | "program": "${workspaceFolder}/node_modules/.bin/jest", 23 | "args": ["${fileBasenameNoExtension}", "--config", "jest.config.js", "--detectOpenHandles"], 24 | "console": "integratedTerminal", 25 | "internalConsoleOptions": "neverOpen", 26 | "disableOptimisticBPs": true, 27 | "windows": { 28 | "program": "${workspaceFolder}/node_modules/jest/bin/jest" 29 | } 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 official_dulin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Learning apollo graphql version 2.x with Node.js 2 | 3 | [![Build Status][travis badge]][travis link] 4 | [![Coverage Status][coveralls badge]][coveralls link] 5 | 6 | Learning [apollographql](https://www.apollographql.com/) version 2.x with Node.js by examples. 7 | 8 | If you are looking for tutorial for Apollo GraphQL version 1.x, please see this [repo](https://github.com/mrdulin/apollo-server-express-starter) 9 | 10 | ## References 11 | 12 | - https://graphql.org/learn/queries/#multiple-fields-in-mutations 13 | - https://github.com/Shopify/graphql-design-tutorial/blob/master/TUTORIAL.md 14 | 15 | [travis badge]: https://travis-ci.org/mrdulin/apollo-graphql-tutorial.svg?branch=master 16 | [travis link]: https://travis-ci.org/mrdulin/apollo-graphql-tutorial 17 | [coveralls badge]: https://coveralls.io/repos/github/mrdulin/apollo-graphql-tutorial/badge.svg?branch=master 18 | [coveralls link]: https://coveralls.io/github/mrdulin/apollo-graphql-tutorial?branch=master 19 | 20 | --- 21 | 22 | Flag Counter 23 | -------------------------------------------------------------------------------- /apollo.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | service: { 3 | endpoint: { 4 | url: 'http://localhost:3000/', 5 | headers: { 6 | authorization: 'Bearer ZOWI', 7 | }, 8 | }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /docs/each-client.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/docs/each-client.png -------------------------------------------------------------------------------- /docs/introduce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/docs/introduce.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | const { defaults } = require('jest-config'); 2 | const pkg = require('./package.json'); 3 | 4 | module.exports = { 5 | automock: false, 6 | bail: 1, 7 | displayName: { 8 | name: pkg.name, 9 | color: 'blue', 10 | }, 11 | preset: 'ts-jest/presets/js-with-ts', 12 | testEnvironment: 'node', 13 | setupFilesAfterEnv: ['./jest.setup.js'], 14 | testMatch: ['**/?(*.)+(spec|test).[jt]s?(x)'], 15 | verbose: true, 16 | }; 17 | -------------------------------------------------------------------------------- /jest.setup.js: -------------------------------------------------------------------------------- 1 | jest.setTimeout(10 * 1000); 2 | -------------------------------------------------------------------------------- /src/01-architecture/@types/index.ts: -------------------------------------------------------------------------------- 1 | export interface IAnyObject { 2 | [property: string]: any; 3 | } 4 | 5 | export type Options = IAnyObject; 6 | export type Command = string | IAnyObject; 7 | export type Parameters = any[] | IAnyObject; 8 | export type DeepPartial = { [P in keyof T]?: DeepPartial }; 9 | export type DataObject = T | DeepPartial; 10 | 11 | export interface ICount { 12 | count: number; 13 | } 14 | export type ID = string | number; 15 | -------------------------------------------------------------------------------- /src/01-architecture/connectors/LowDBConnectorImpl.ts: -------------------------------------------------------------------------------- 1 | import low from 'lowdb'; 2 | import FileSync from 'lowdb/adapters/FileSync'; 3 | 4 | import { IConnector } from './connector'; 5 | import { IAnyObject, ID } from '../@types'; 6 | 7 | interface ILowDbConnector extends IConnector { 8 | findById(tableName: string, id: ID): Promise; 9 | } 10 | 11 | class LowDbConnectorImpl implements ILowDbConnector { 12 | private db; 13 | constructor(config: IAnyObject) { 14 | const adapter = new FileSync(config.source); 15 | this.db = low(adapter); 16 | } 17 | public async connect() { 18 | // 19 | } 20 | public async disconnect() { 21 | // 22 | } 23 | // TODO: Model table map, then don't need tableName and specify the identity key 24 | public findById(tableName: string, id: string) { 25 | return this.db(tableName).find({ user_id: id }); 26 | } 27 | } 28 | 29 | export { LowDbConnectorImpl, ILowDbConnector }; 30 | -------------------------------------------------------------------------------- /src/01-architecture/connectors/connector.ts: -------------------------------------------------------------------------------- 1 | import { IAnyObject, Options, Command, Parameters } from '../@types'; 2 | import { EntityData } from '../models/model'; 3 | 4 | export interface IConnector { 5 | connect(): Promise; 6 | disconnect(): Promise; 7 | execute?(command: Command, parameters: Parameters, options?: Options): Promise; 8 | } 9 | 10 | export interface ICURDConnector extends IConnector { 11 | create(entity: EntityData, options?: Options): Promise>; 12 | update(entity: EntityData, options?: Options): Promise; 13 | find(filter?: any, options?: Options): Promise>>; 14 | delete(entity?: EntityData, options?: Options): Promise; 15 | } 16 | -------------------------------------------------------------------------------- /src/01-architecture/datasources/DataSourceImpl.ts: -------------------------------------------------------------------------------- 1 | import { IDataSource } from './datasource'; 2 | import { IConnector } from '../connectors/connector'; 3 | import { IAnyObject } from '../@types'; 4 | import { LowDbConnectorImpl } from '../connectors/LowDBConnectorImpl'; 5 | 6 | interface IDataSourceConfig { 7 | name: 'lowdb'; 8 | settings: IAnyObject; 9 | } 10 | 11 | class DataSourceImpl implements IDataSource { 12 | private name: string; 13 | private connector?: IConnector; 14 | private settings: IAnyObject; 15 | constructor(config: IDataSourceConfig) { 16 | this.name = config.name; 17 | this.settings = config.settings; 18 | } 19 | public async initialize() { 20 | switch (this.name) { 21 | case 'lowdb': 22 | this.connector = new LowDbConnectorImpl(this.settings); 23 | break; 24 | } 25 | if (!this.connector) { 26 | throw new Error(`connector ${this.name} doesn't exists`); 27 | } 28 | try { 29 | await this.connector.connect(); 30 | } catch (error) { 31 | console.error(error); 32 | throw error; 33 | } 34 | } 35 | public getConnector() { 36 | if (!this.connector) { 37 | throw new Error(`connector ${this.name} doesn't exists`); 38 | } 39 | return this.connector; 40 | } 41 | } 42 | 43 | export { DataSourceImpl }; 44 | -------------------------------------------------------------------------------- /src/01-architecture/datasources/datasource.ts: -------------------------------------------------------------------------------- 1 | import { IConnector } from '../connectors/connector'; 2 | 3 | export interface IDataSource { 4 | getConnector(): IConnector; 5 | } 6 | -------------------------------------------------------------------------------- /src/01-architecture/main.ts: -------------------------------------------------------------------------------- 1 | import { UserRepositoryImpl } from './repositories/User/UserRepositoryImpl'; 2 | import { DataSourceImpl } from './datasources/DataSourceImpl'; 3 | import { createServer, IAppContext } from './server'; 4 | import { Context } from 'apollo-server-core'; 5 | import { IUserRepsitory } from './repositories/User/UserRepsitory'; 6 | import { UserEntityData } from './models/User/UserEntity'; 7 | 8 | async function main() { 9 | const lowDbDataSource = new DataSourceImpl({ name: 'lowdb', settings: {} }); 10 | await lowDbDataSource.initialize(); 11 | 12 | const contextFunction = (): Context => { 13 | const userRepositoryImpl: IUserRepsitory = new UserRepositoryImpl(lowDbDataSource); 14 | return { 15 | userRepositoryImpl, 16 | }; 17 | }; 18 | 19 | await createServer({ PORT: process.env.PORT || '3000', contextFunction }); 20 | } 21 | 22 | if (require.main === module) { 23 | main(); 24 | } 25 | -------------------------------------------------------------------------------- /src/01-architecture/models/Address/AddressEntity.ts: -------------------------------------------------------------------------------- 1 | export interface IAddressEntity { 2 | address_country: string; 3 | address_city: string; 4 | address_street: string; 5 | } 6 | -------------------------------------------------------------------------------- /src/01-architecture/models/User/UserEntity.ts: -------------------------------------------------------------------------------- 1 | import { IAddressEntity } from '../Address/AddressEntity'; 2 | import { EntityData } from '../model'; 3 | 4 | export interface IUserEntity { 5 | user_id: string; 6 | user_nme: string; 7 | user_email: string; 8 | user_address: IAddressEntity; 9 | } 10 | 11 | export type UserEntityData = EntityData; 12 | -------------------------------------------------------------------------------- /src/01-architecture/models/model.ts: -------------------------------------------------------------------------------- 1 | import { DataObject } from '../@types'; 2 | 3 | export type EntityData = DataObject; 4 | -------------------------------------------------------------------------------- /src/01-architecture/readme.md: -------------------------------------------------------------------------------- 1 | # project architecture 2 | 3 | still in progress... 4 | -------------------------------------------------------------------------------- /src/01-architecture/repositories/User/UserRepositoryImpl.ts: -------------------------------------------------------------------------------- 1 | import { IUserRepsitory } from './UserRepsitory'; 2 | import { ID } from '../../@types'; 3 | import { IDataSource } from '../../datasources/datasource'; 4 | import { UserEntityData } from '../../models/User/UserEntity'; 5 | import { ILowDbConnector } from '../../connectors/LowDBConnectorImpl'; 6 | 7 | class UserRepositoryImpl implements IUserRepsitory { 8 | private connector: ILowDbConnector; 9 | constructor(dataSource: IDataSource) { 10 | this.connector = dataSource.getConnector() as ILowDbConnector; 11 | } 12 | public async findById(id: ID): Promise { 13 | return this.connector.findById('users', id); 14 | } 15 | } 16 | 17 | export { UserRepositoryImpl }; 18 | -------------------------------------------------------------------------------- /src/01-architecture/repositories/User/UserRepsitory.ts: -------------------------------------------------------------------------------- 1 | import { IRepository } from '../repository'; 2 | import { ID } from '../../@types'; 3 | 4 | export interface IUserRepsitory extends IRepository { 5 | findById(id: ID): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/01-architecture/repositories/repository.ts: -------------------------------------------------------------------------------- 1 | import { Command, IAnyObject, Options, Parameters, ICount } from '../@types'; 2 | 3 | export interface IRepository { 4 | execute?(command: Command, parameters: Parameters, options?: Options): Promise; 5 | } 6 | 7 | export interface ICURDRepository extends IRepository { 8 | create(dataObject: any, options?: Options): Promise; 9 | update(dataObject: any, where?: any, options?: Options): Promise; 10 | find(filter?: any, options?: Options): Promise; 11 | delete(where?: any, options?: Options): Promise; 12 | } 13 | -------------------------------------------------------------------------------- /src/01-architecture/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'graphql-tools'; 2 | import { IAppContext } from './server'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | userById: (_, { id }, { userRepositoryImpl }: IAppContext) => { 7 | return userRepositoryImpl.findById(id); 8 | }, 9 | }, 10 | }; 11 | 12 | export { resolvers }; 13 | -------------------------------------------------------------------------------- /src/01-architecture/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { ContextFunction } from 'apollo-server-core'; 3 | import http from 'http'; 4 | 5 | import { typeDefs } from './typeDefs'; 6 | import { resolvers } from './resolvers'; 7 | import { IUserRepsitory } from './repositories/User/UserRepsitory'; 8 | import { UserEntityData } from './models/User/UserEntity'; 9 | 10 | interface IAppContext { 11 | userRepositoryImpl: IUserRepsitory; 12 | } 13 | 14 | interface IServerOptions { 15 | PORT: string | number; 16 | contextFunction: ContextFunction; 17 | } 18 | 19 | async function createServer(options: IServerOptions): Promise { 20 | const { PORT, contextFunction } = options; 21 | 22 | const server = new ApolloServer({ 23 | typeDefs, 24 | resolvers, 25 | context: contextFunction, 26 | }); 27 | 28 | return server 29 | .listen(PORT) 30 | .then(({ url }: ServerInfo) => { 31 | console.log(`🚀 Server ready at ${url}`); 32 | }) 33 | .catch((error) => { 34 | console.error('Create server failed.'); 35 | console.error(error); 36 | }); 37 | } 38 | 39 | export { createServer, IServerOptions, IAppContext }; 40 | -------------------------------------------------------------------------------- /src/01-architecture/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | userId: ID! 6 | userNme: String 7 | userEmail: String 8 | userAddress: Address 9 | } 10 | 11 | type Address { 12 | addressCountry: String 13 | addressCity: String 14 | addressStreet: String 15 | } 16 | 17 | type Query { 18 | userById(id: ID!): User 19 | } 20 | `; 21 | 22 | export { typeDefs }; 23 | -------------------------------------------------------------------------------- /src/02-architecture/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | PORT: process.env.PORT || '3000', 3 | GRAPHQL_ENDPOINT: '/graphql', 4 | }; 5 | 6 | export { config }; 7 | -------------------------------------------------------------------------------- /src/02-architecture/connectors/connector.ts: -------------------------------------------------------------------------------- 1 | export interface IConnector { 2 | connect(): DB; 3 | } 4 | -------------------------------------------------------------------------------- /src/02-architecture/connectors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './postgresql.connector'; 2 | -------------------------------------------------------------------------------- /src/02-architecture/connectors/postgresql.connector.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | import { credentials } from '../credentials'; 3 | import { IConnector } from './connector'; 4 | import { camelizeKeys } from '../util'; 5 | 6 | type DB = Knex; 7 | 8 | class PostgreSQLConnector implements IConnector { 9 | public connect(): DB { 10 | const config: Knex.Config = { 11 | client: 'pg', 12 | connection: { 13 | host: credentials.SQL_HOST, 14 | port: Number.parseInt(credentials.SQL_PORT, 10), 15 | database: credentials.SQL_DATABASE, 16 | user: credentials.SQL_USER, 17 | password: credentials.SQL_PASSWORD, 18 | }, 19 | pool: { 20 | min: 1, 21 | max: 1, 22 | }, 23 | debug: process.env.NODE_ENV !== 'production', 24 | postProcessResponse: this.postProcessResponse, 25 | }; 26 | return Knex(config); 27 | } 28 | 29 | private postProcessResponse(result: any, queryContext: any) { 30 | if (result.rows && result.rows.length) { 31 | result.rows = result.rows.map((row: any) => { 32 | return camelizeKeys(row); 33 | }); 34 | return result; 35 | } 36 | return camelizeKeys(result); 37 | } 38 | } 39 | 40 | export { PostgreSQLConnector, DB }; 41 | -------------------------------------------------------------------------------- /src/02-architecture/credentials.ts: -------------------------------------------------------------------------------- 1 | interface ICredentials { 2 | SQL_HOST: string; 3 | SQL_PORT: string; 4 | SQL_DATABASE: string; 5 | SQL_USER: string; 6 | SQL_PASSWORD: string; 7 | APOLLO_ENGINE_API_KEY: string; 8 | TRACE_AGENT_CREDENTIAL: string; 9 | } 10 | 11 | export const credentials: ICredentials = { 12 | SQL_HOST: process.env.SQL_HOST || '127.0.0.1', 13 | SQL_PORT: process.env.SQL_PORT || '5432', 14 | SQL_DATABASE: process.env.SQL_DATABASE || 'postgres', 15 | SQL_USER: process.env.SQL_USER || 'postgres', 16 | SQL_PASSWORD: process.env.SQL_PASSWORD || '', 17 | APOLLO_ENGINE_API_KEY: process.env.APOLLO_ENGINE_API_KEY || '', 18 | TRACE_AGENT_CREDENTIAL: process.env.TRACE_AGENT_CREDENTIAL || '', 19 | }; 20 | 21 | export { ICredentials }; 22 | -------------------------------------------------------------------------------- /src/02-architecture/datasources/index.ts: -------------------------------------------------------------------------------- 1 | export * from './postgresql.datasource'; 2 | -------------------------------------------------------------------------------- /src/02-architecture/datasources/postgresql.datasource.ts: -------------------------------------------------------------------------------- 1 | import { DataSource, DataSourceConfig } from 'apollo-datasource'; 2 | import { PostgreSQLConnector, DB } from '../connectors'; 3 | import DataLoader from 'dataloader'; 4 | 5 | class PostgresSQLDataCource extends DataSource { 6 | public context!: TContext; 7 | public db!: DB; 8 | public initialize(config: DataSourceConfig): void { 9 | const connector = new PostgreSQLConnector(); 10 | this.context = config.context; 11 | this.db = connector.connect(); 12 | } 13 | 14 | public createLoader(batchLoadFn: DataLoader.BatchLoadFn): DataLoader { 15 | return new DataLoader(batchLoadFn); 16 | } 17 | } 18 | 19 | export { PostgresSQLDataCource, DataLoader }; 20 | -------------------------------------------------------------------------------- /src/02-architecture/db/knexfile.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { credentials } from '../credentials'; 3 | 4 | const settings = { 5 | development: { 6 | client: 'postgresql', 7 | connection: { 8 | host: credentials.SQL_HOST, 9 | port: credentials.SQL_PORT, 10 | database: credentials.SQL_DATABASE, 11 | user: credentials.SQL_USER, 12 | password: credentials.SQL_PASSWORD, 13 | }, 14 | debug: true, 15 | pool: { 16 | min: 2, 17 | max: 20, 18 | }, 19 | migrations: { 20 | directory: path.resolve(__dirname, './migrations'), 21 | }, 22 | seeds: { 23 | directory: path.resolve(__dirname, './seeds'), 24 | }, 25 | }, 26 | }; 27 | 28 | module.exports = settings; 29 | -------------------------------------------------------------------------------- /src/02-architecture/db/migrations/20190518163408_db.ts: -------------------------------------------------------------------------------- 1 | import * as Knex from 'knex'; 2 | 3 | export async function up(knex: Knex): Promise { 4 | await knex.schema.createTable('addresses', (t: Knex.TableBuilder) => { 5 | t.increments('address_id'); 6 | ['address_city', 'address_country', 'address_state'].forEach((col: string) => { 7 | t.string(col); 8 | }); 9 | }); 10 | await knex.schema.createTable('users', (t: Knex.TableBuilder) => { 11 | t.increments('user_id'); 12 | t.string('user_nme').nullable(); 13 | t.string('user_email') 14 | .unique() 15 | .notNullable(); 16 | 17 | t.integer('user_address_id').unsigned(); 18 | t.foreign('user_address_id') 19 | .references('address_id') 20 | .inTable('addresses'); 21 | }); 22 | 23 | await knex.schema.createTable('posts', (t: Knex.TableBuilder) => { 24 | t.increments('post_id'); 25 | t.string('post_title', 100) 26 | .unique() 27 | .notNullable(); 28 | t.text('post_content', 'longtext').notNullable(); 29 | t.timestamp('post_created_at').defaultTo(knex.fn.now()); 30 | t.integer('post_author_id').unsigned(); 31 | t.foreign('post_author_id') 32 | .references('user_id') 33 | .inTable('users'); 34 | }); 35 | } 36 | 37 | export async function down(knex: Knex): Promise { 38 | await Promise.all(['users', 'addresses'].map((tableName: string) => knex.schema.dropTable(tableName))); 39 | } 40 | -------------------------------------------------------------------------------- /src/02-architecture/db/models/User.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable:variable-name 2 | class User { 3 | public user_id: number = 0; 4 | public user_nme: string = ''; 5 | public user_email: string = ''; 6 | public user_address_id: number = 0; 7 | } 8 | 9 | export { User }; 10 | -------------------------------------------------------------------------------- /src/02-architecture/db/models/index.ts: -------------------------------------------------------------------------------- 1 | export * from './User'; 2 | -------------------------------------------------------------------------------- /src/02-architecture/db/seeds/db.ts: -------------------------------------------------------------------------------- 1 | import * as Knex from 'knex'; 2 | import faker from 'faker'; 3 | 4 | export async function seed(knex: Knex): Promise { 5 | await knex('addresses').insert([ 6 | { 7 | address_id: 1, 8 | address_city: faker.address.city(), 9 | address_country: faker.address.country(), 10 | address_state: faker.address.state(), 11 | }, 12 | { 13 | address_id: 2, 14 | address_city: faker.address.city(), 15 | address_country: faker.address.country(), 16 | address_state: faker.address.state(), 17 | }, 18 | ]); 19 | await knex('users').insert([ 20 | { user_id: 1, user_nme: faker.name.findName(), user_email: faker.internet.email(), user_address_id: 1 }, 21 | { user_id: 2, user_nme: faker.name.findName(), user_email: faker.internet.email(), user_address_id: 1 }, 22 | { user_id: 3, user_nme: faker.name.findName(), user_email: faker.internet.email(), user_address_id: 2 }, 23 | ]); 24 | 25 | const posts: any[] = []; 26 | for (let i = 0; i < 10; i++) { 27 | const post = { 28 | post_title: faker.lorem.sentence(), 29 | post_content: faker.lorem.paragraphs(5), 30 | post_author_id: 1, 31 | }; 32 | posts.push(post); 33 | } 34 | await knex('posts').insert(posts); 35 | } 36 | -------------------------------------------------------------------------------- /src/02-architecture/directives/camelizeKeys/camelizeKeys.directive.ts: -------------------------------------------------------------------------------- 1 | import { SchemaDirectiveVisitor } from 'graphql-tools'; 2 | import { GraphQLObjectType } from 'graphql'; 3 | 4 | class CamelizeKeysDirective extends SchemaDirectiveVisitor { 5 | public visitObject(object: GraphQLObjectType) { 6 | const fields = object.getFields(); 7 | console.log('directive works'); 8 | } 9 | } 10 | 11 | export { CamelizeKeysDirective }; 12 | -------------------------------------------------------------------------------- /src/02-architecture/directives/camelizeKeys/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeDefs'; 2 | export * from './camelizeKeys.directive'; 3 | -------------------------------------------------------------------------------- /src/02-architecture/directives/camelizeKeys/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | directive @camelizeKeys on OBJECT 5 | `; 6 | 7 | export { typeDefs }; 8 | -------------------------------------------------------------------------------- /src/02-architecture/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.1' 2 | services: 3 | pg: 4 | image: postgres:9.6 5 | restart: always 6 | ports: 7 | - 5431:5432 8 | environment: 9 | POSTGRES_DB: ${SQL_DATABASE} 10 | POSTGRES_USER: ${SQL_USER} 11 | POSTGRES_PASSWORD: ${SQL_PASSWORD} 12 | -------------------------------------------------------------------------------- /src/02-architecture/docs/google-cloud-stackdriver-trace-integrate-graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/02-architecture/docs/google-cloud-stackdriver-trace-integrate-graphql.png -------------------------------------------------------------------------------- /src/02-architecture/docs/knex-post-process-response.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/02-architecture/docs/knex-post-process-response.png -------------------------------------------------------------------------------- /src/02-architecture/docs/trace-with-dataloader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/02-architecture/docs/trace-with-dataloader.png -------------------------------------------------------------------------------- /src/02-architecture/docs/trace-without-dataloader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/02-architecture/docs/trace-without-dataloader.png -------------------------------------------------------------------------------- /src/02-architecture/main.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | if (process.env.NODE_ENV !== 'production') { 4 | // tslint:disable-next-line: no-var-requires 5 | const dotenv = require('dotenv'); 6 | const dotenvConfigOutput = dotenv.config({ path: path.resolve(__dirname, './.env') }); 7 | if (dotenvConfigOutput.error) { 8 | console.error(dotenvConfigOutput.error); 9 | process.exit(1); 10 | } 11 | } 12 | // tslint:disable-next-line:no-var-requires 13 | require('./traceAgent'); 14 | 15 | import { createServer } from './server'; 16 | import { config } from './config'; 17 | import { credentials } from './credentials'; 18 | 19 | async function bootstrap() { 20 | console.log(`config: ${JSON.stringify(config, null, 2)}`); 21 | console.log(`credentials: ${JSON.stringify(credentials, null, 2)}`); 22 | 23 | await createServer({ PORT: config.PORT, GRAPHQL_ENDPOINT: config.GRAPHQL_ENDPOINT }); 24 | } 25 | 26 | bootstrap(); 27 | -------------------------------------------------------------------------------- /src/02-architecture/middleware/authenticate.middleware.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationError } from 'apollo-server'; 2 | const token = 'secret token 666'; 3 | 4 | const authenticateMiddleware = async (resolve, parent, args, ctx, info) => { 5 | const permit = ctx.request.get('Authorization') === token; 6 | 7 | if (!permit) { 8 | throw new AuthenticationError(`Not authorised!`); 9 | } 10 | 11 | return resolve(); 12 | }; 13 | 14 | export { authenticateMiddleware }; 15 | -------------------------------------------------------------------------------- /src/02-architecture/middleware/fields.middleware.ts: -------------------------------------------------------------------------------- 1 | import graphqlFields from 'graphql-fields'; 2 | import { snakeCase } from 'lodash'; 3 | 4 | const selectFieldsMiddleware = async (resolve, parent, args, ctx, info) => { 5 | const selectFields = Object.keys(graphqlFields(info)); 6 | ctx.selectFields = selectFields.map(snakeCase); 7 | return resolve(parent, args, ctx, info); 8 | }; 9 | 10 | export { selectFieldsMiddleware }; 11 | -------------------------------------------------------------------------------- /src/02-architecture/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './log.middleware'; 2 | export * from './authenticate.middleware'; 3 | export * from './fields.middleware'; 4 | export * from './trace.middleware'; 5 | -------------------------------------------------------------------------------- /src/02-architecture/middleware/log.middleware.ts: -------------------------------------------------------------------------------- 1 | import { IMiddleware } from 'graphql-middleware'; 2 | import { GraphQLResolveInfo } from 'graphql'; 3 | import { logger } from '../../util'; 4 | 5 | const logMiddleware: IMiddleware = async ( 6 | // tslint:disable-next-line:ban-types 7 | resolve: Function, 8 | parent: any, 9 | args: any, 10 | context: any, 11 | info: GraphQLResolveInfo, 12 | ) => { 13 | try { 14 | logger.debug('Input', { context: info.fieldName, arguments: args }); 15 | const res = await resolve(parent, args, context, info); 16 | logger.debug(`Output`, { context: info.fieldName, extra: res }); 17 | return res; 18 | } catch (e) { 19 | logger.error(e); 20 | } 21 | }; 22 | 23 | export { logMiddleware }; 24 | -------------------------------------------------------------------------------- /src/02-architecture/middleware/trace.middleware.ts: -------------------------------------------------------------------------------- 1 | import { IMiddleware } from 'graphql-middleware'; 2 | import { GraphQLResolveInfo } from 'graphql'; 3 | // tslint:disable-next-line:no-var-requires 4 | const { tracer } = require('../traceAgent'); 5 | 6 | const traceMiddleware: IMiddleware = async ( 7 | // tslint:disable-next-line:ban-types 8 | resolve: Function, 9 | parent: any, 10 | args: any, 11 | context: any, 12 | info: GraphQLResolveInfo, 13 | ) => { 14 | const graphqlOperations = ['Query', 'Mutation', 'Subscrpition']; 15 | if (graphqlOperations.includes(info.parentType.name)) { 16 | const resolveTrace = tracer.createChildSpan({ name: info.fieldName }); 17 | resolveTrace.addLabel('operation', info.operation.operation); 18 | resolveTrace.addLabel('args', JSON.stringify(args)); 19 | const res = await resolve(parent, args, context, info); 20 | resolveTrace.endSpan(); 21 | return res; 22 | } else { 23 | const res = await resolve(parent, args, context, info); 24 | return res; 25 | } 26 | }; 27 | 28 | export { traceMiddleware }; 29 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/AddressDataSource.ts: -------------------------------------------------------------------------------- 1 | export interface IAddressDataSource { 2 | findById(id: string): Promise; 3 | } 4 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/AddressDataSourceImpl.ts: -------------------------------------------------------------------------------- 1 | import { PostgresSQLDataCource } from '../../datasources'; 2 | import { IAddressDataSource } from './AddressDataSource'; 3 | 4 | class AddressDataSourceImpl extends PostgresSQLDataCource implements IAddressDataSource { 5 | constructor() { 6 | super(); 7 | } 8 | public async findById(id: string) { 9 | return this.db('addresses') 10 | .where({ address_id: id }) 11 | .first(); 12 | } 13 | } 14 | 15 | export { AddressDataSourceImpl }; 16 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeDefs'; 2 | export * from './resolvers'; 3 | export * from './middlewareTypeMap'; 4 | export * from './AddressDataSourceImpl'; 5 | export * from './AddressDataSource'; 6 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/middlewareTypeMap.ts: -------------------------------------------------------------------------------- 1 | import { logMiddleware } from '../../middleware'; 2 | import { IMiddlewareTypeMap } from 'graphql-middleware'; 3 | import { mergeDeep } from 'apollo-utilities'; 4 | 5 | const logMiddlewareTypeMap: IMiddlewareTypeMap = { 6 | Query: { 7 | addressById: logMiddleware, 8 | }, 9 | }; 10 | 11 | const addressMiddlewareTypeMap = mergeDeep(logMiddlewareTypeMap); 12 | 13 | export { addressMiddlewareTypeMap }; 14 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import { IAppContext } from '../../server'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | addressById: (_, { id }, { dataSources }: IAppContext) => { 7 | return dataSources.address.findById(id); 8 | }, 9 | }, 10 | }; 11 | 12 | export { resolvers }; 13 | -------------------------------------------------------------------------------- /src/02-architecture/modules/address/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Address { 5 | addressId: ID! 6 | addressCountry: String 7 | addressState: String 8 | addressCity: String 9 | } 10 | 11 | extend type Query { 12 | addressById(id: ID!): Address 13 | } 14 | `; 15 | 16 | export { typeDefs }; 17 | -------------------------------------------------------------------------------- /src/02-architecture/modules/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeDefs'; 2 | -------------------------------------------------------------------------------- /src/02-architecture/modules/common/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type CommonResponse { 5 | code: Int! 6 | message: String! 7 | } 8 | `; 9 | 10 | export { typeDefs }; 11 | -------------------------------------------------------------------------------- /src/02-architecture/modules/post/PostDataSource.ts: -------------------------------------------------------------------------------- 1 | export interface IPostDataSource { 2 | findById(id: string): Promise; 3 | find(): Promise; 4 | insert(post: any): Promise; 5 | } 6 | -------------------------------------------------------------------------------- /src/02-architecture/modules/post/PostDataSourceImpl.ts: -------------------------------------------------------------------------------- 1 | import { IPostDataSource } from './PostDataSource'; 2 | import { PostgresSQLDataCource } from '../../datasources'; 3 | 4 | class PostDataSourceImpl extends PostgresSQLDataCource implements IPostDataSource { 5 | constructor() { 6 | super(); 7 | } 8 | public async findById(id: string): Promise { 9 | const sql = ` 10 | SELECT * FROM posts WHERE post_id = ? 11 | `; 12 | return this.db.raw(sql, [id]).then((res) => res.rows); 13 | } 14 | 15 | public async find() { 16 | const sql = ` 17 | SELECT * FROM posts; 18 | `; 19 | return this.db.raw(sql).then((res) => res.rows); 20 | } 21 | 22 | public async insert(post: any) { 23 | const postPO = { 24 | post_title: post.postTitle, 25 | post_content: post.postContent, 26 | post_author_id: post.postAuthorId, 27 | }; 28 | return this.db('posts') 29 | .insert(postPO) 30 | .then(() => ({ code: 0, message: 'insert post done' })); 31 | } 32 | } 33 | 34 | export { PostDataSourceImpl }; 35 | -------------------------------------------------------------------------------- /src/02-architecture/modules/post/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeDefs'; 2 | export * from './PostDataSource'; 3 | export * from './PostDataSourceImpl'; 4 | export * from './resolvers'; 5 | -------------------------------------------------------------------------------- /src/02-architecture/modules/post/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import { IAppContext } from '../../server'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | postById: (_, { id }, { dataSources }: IAppContext) => { 7 | return dataSources.post.findById(id); 8 | }, 9 | posts: (_, __, { dataSources }: IAppContext) => { 10 | return dataSources.post.find(); 11 | }, 12 | }, 13 | Post: { 14 | postAuthor: (source, _, { dataSources }: IAppContext) => { 15 | return dataSources.user.findById(source.postAuthorId); 16 | }, 17 | }, 18 | Mutation: { 19 | addPost: (_, { post }, { dataSources }: IAppContext) => { 20 | return dataSources.post.insert(post); 21 | }, 22 | }, 23 | }; 24 | 25 | export { resolvers }; 26 | -------------------------------------------------------------------------------- /src/02-architecture/modules/post/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Post { 5 | postId: ID! 6 | postTitle: String! 7 | postContent: String! 8 | postCreatedAt: String! 9 | postAuthorId: ID! 10 | postAuthor: User 11 | } 12 | 13 | input PostInput { 14 | postTitle: String! 15 | postContent: String! 16 | postAuthorId: ID! 17 | } 18 | 19 | extend type Query { 20 | postById(id: ID!): Post 21 | posts: [Post]! 22 | } 23 | 24 | extend type Mutation { 25 | addPost(post: PostInput!): CommonResponse! 26 | } 27 | `; 28 | 29 | export { typeDefs }; 30 | -------------------------------------------------------------------------------- /src/02-architecture/modules/user/UserDataSource.ts: -------------------------------------------------------------------------------- 1 | export interface IUserDataSource { 2 | findById(id: string): Promise; 3 | findAll(): Promise; 4 | } 5 | -------------------------------------------------------------------------------- /src/02-architecture/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './typeDefs'; 2 | export * from './resolvers'; 3 | export * from './middlewareTypeMap'; 4 | export * from './UserDataSource'; 5 | export * from './UserDataSourceImpl'; 6 | -------------------------------------------------------------------------------- /src/02-architecture/modules/user/middlewareTypeMap.ts: -------------------------------------------------------------------------------- 1 | import { logMiddleware, authenticateMiddleware } from '../../middleware'; 2 | import { IMiddlewareTypeMap } from 'graphql-middleware'; 3 | import { mergeDeep } from 'apollo-utilities'; 4 | 5 | const logMiddlewareTypeMap: IMiddlewareTypeMap = { 6 | Query: { 7 | userById: logMiddleware, 8 | }, 9 | }; 10 | 11 | const authenticationMiddlewareTypeMap: IMiddlewareTypeMap = { 12 | Query: { 13 | users: authenticateMiddleware, 14 | }, 15 | }; 16 | 17 | const userMiddlewareTypeMap = mergeDeep(logMiddlewareTypeMap, authenticationMiddlewareTypeMap); 18 | 19 | export { userMiddlewareTypeMap }; 20 | -------------------------------------------------------------------------------- /src/02-architecture/modules/user/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import { IAppContext } from '../../server'; 3 | import fetch from 'node-fetch'; 4 | import { logger } from '../../../util'; 5 | 6 | const resolvers: IResolvers = { 7 | Query: { 8 | userById: async (_, { id }, { dataSources }: IAppContext) => { 9 | // test GCP cloud trace 10 | const response = await fetch('https://api.itbook.store/1.0/search/mongodb').then((res) => res.json()); 11 | logger.debug('HTTP request for testing GCP cloud trace', { arguments: { response } }); 12 | return dataSources.user.findById(id); 13 | }, 14 | users: (_, __, { dataSources }: IAppContext) => { 15 | return dataSources.user.findAll(); 16 | }, 17 | }, 18 | User: { 19 | userAddress: (source, _, { dataSources }: IAppContext) => { 20 | return dataSources.address.findById(source.userAddressId); 21 | }, 22 | }, 23 | }; 24 | 25 | export { resolvers }; 26 | -------------------------------------------------------------------------------- /src/02-architecture/modules/user/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User @camelizeKeys { 5 | userId: ID! 6 | userNme: String 7 | userEmail: String! 8 | userAddress: Address 9 | } 10 | 11 | extend type Query { 12 | userById(id: ID!): User 13 | users: [User]! 14 | } 15 | `; 16 | 17 | export { typeDefs }; 18 | -------------------------------------------------------------------------------- /src/02-architecture/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "architecture-02", 3 | "version": "1.0.0", 4 | "description": "change directory", 5 | "main": "./dist/architecture-02/main.js", 6 | "directories": { 7 | "doc": "docs" 8 | }, 9 | "scripts": { 10 | "dev": "npm run build && npm start", 11 | "prebuild": "npm run clean", 12 | "copy": "cp ./.env ./dist/architecture-02", 13 | "build": "../../node_modules/.bin/tsc -p .", 14 | "postbuild": "npm run copy", 15 | "start": "node ./dist/architecture-02/main.js", 16 | "clean": "rm -rf ./dist" 17 | }, 18 | "keywords": [], 19 | "author": "mrdulin ", 20 | "license": "MIT" 21 | } 22 | -------------------------------------------------------------------------------- /src/02-architecture/readme.md: -------------------------------------------------------------------------------- 1 | # schema-column-mapping 2 | 3 | change directory 4 | 5 | ```bash 6 | cd src/architecture-02 7 | ``` 8 | 9 | create `.env` file: 10 | 11 | ```bash 12 | 13 | SQL_DATABASE=nodejs-pg-knex-samples 14 | SQL_USER=sampleadmin 15 | SQL_PASSWORD=samplepass 16 | ``` 17 | 18 | create db migration file 19 | 20 | ```bash 21 | cd ./db 22 | npx knex --knexfile ./knexfile.ts migrate:make db -x ts 23 | ``` 24 | 25 | db migrate 26 | 27 | ```bash 28 | cd ./db 29 | npx knex --knexfile ./knexfile.ts migrate:latest db -x ts 30 | ``` 31 | 32 | seed data 33 | 34 | ```bash 35 | cd ./db 36 | npx knex --knexfile ./knexfile.ts seed:run 37 | ``` 38 | 39 | start `GraphQL` server: 40 | 41 | ```bash 42 | cd ./src/architecture-02 43 | npm run watch -- ./src/architecture-02/server.ts 44 | ``` 45 | -------------------------------------------------------------------------------- /src/02-architecture/traceAgent.ts: -------------------------------------------------------------------------------- 1 | // tslint:disable-next-line:no-var-requires 2 | const pkg = require('../../package.json'); 3 | // tslint:disable-next-line:no-var-requires 4 | const traceAgent = require('@google-cloud/trace-agent'); 5 | const tracer = traceAgent.start({ 6 | projectId: process.env.PROJECT_ID, 7 | keyFilename: process.env.TRACE_AGENT_CREDENTIAL, 8 | ignoreUrls: [/^\/\.well-known/, '^/$', '/favicon.ico'], 9 | ignoreMethods: ['options'], 10 | enabled: !process.env.DISABLE_GCP_TRACING, 11 | serviceContext: { 12 | service: pkg.name, 13 | version: pkg.version, 14 | }, 15 | }); 16 | 17 | module.exports = { tracer, traceAgent }; 18 | -------------------------------------------------------------------------------- /src/02-architecture/util.ts: -------------------------------------------------------------------------------- 1 | import { camelCase, snakeCase } from 'lodash'; 2 | 3 | const camelizeKeys = (obj) => { 4 | if (Array.isArray(obj)) { 5 | return obj.map((v) => camelizeKeys(v)); 6 | } else if (obj !== null && obj.constructor === Object) { 7 | return Object.keys(obj).reduce( 8 | (result, key) => ({ 9 | ...result, 10 | [camelCase(key)]: camelizeKeys(obj[key]), 11 | }), 12 | {}, 13 | ); 14 | } 15 | return obj; 16 | }; 17 | 18 | const snakeCaseKeys = (obj) => { 19 | if (Array.isArray(obj)) { 20 | return obj.map((v) => snakeCaseKeys(v)); 21 | } else if (obj !== null && obj.constructor === Object) { 22 | return Object.keys(obj).reduce( 23 | (result, key) => ({ 24 | ...result, 25 | [snakeCase(key)]: snakeCaseKeys(obj[key]), 26 | }), 27 | {}, 28 | ); 29 | } 30 | return obj; 31 | }; 32 | 33 | export { camelizeKeys, snakeCaseKeys }; 34 | -------------------------------------------------------------------------------- /src/apollo-server-plugins/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document 7 | 8 | 9 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/apollo-server-plugins/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server-express'; 2 | import express from 'express'; 3 | import path from 'path'; 4 | import httpHeadersPlugin from './apollo-server-plugins-http-header'; 5 | 6 | const typeDefs = gql` 7 | type Query { 8 | hello: String 9 | } 10 | `; 11 | const resolvers = { 12 | Query: { 13 | hello: async (parent, args, context, info) => { 14 | context.setCookies.push({ 15 | name: 'cookieName', 16 | value: 'cookieContent', 17 | options: { 18 | expires: new Date('2021-01-01T00:00:00'), 19 | httpOnly: true, 20 | maxAge: 3600, 21 | path: '/', 22 | sameSite: true, 23 | secure: true, 24 | }, 25 | }); 26 | return 'Hello world!'; 27 | }, 28 | }, 29 | }; 30 | 31 | const app = express(); 32 | const port = 4000; 33 | const server = new ApolloServer({ 34 | typeDefs, 35 | resolvers, 36 | plugins: [httpHeadersPlugin], 37 | context: { 38 | setHeaders: new Array(), 39 | setCookies: new Array(), 40 | }, 41 | }); 42 | server.applyMiddleware({ app, path: '/graphql', bodyParserConfig: true }); 43 | app.get('/', (req, res) => { 44 | const file = path.resolve(__dirname, './index.html'); 45 | res.sendFile(file); 46 | }); 47 | app.listen(port, () => { 48 | console.log(`🚀 Server ready at http://localhost:${port}/graphql`); 49 | }); 50 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/issue/middleware.ts: -------------------------------------------------------------------------------- 1 | const logCount = async (resolve, root, args, context, info) => { 2 | console.count('log middleware execute count'); 3 | const result = await resolve(root, args, context, info); 4 | return result; 5 | }; 6 | 7 | export { logCount }; 8 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/issue/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { mergeSchemas } from 'apollo-server'; 2 | import { schema1 } from './schema1'; 3 | import { schema2 } from './schema2'; 4 | import { applyMiddleware } from 'graphql-middleware'; 5 | import { logCount } from '../middleware'; 6 | 7 | let schema = mergeSchemas({ schemas: [schema1, schema2] }); 8 | schema = applyMiddleware(schema, logCount); 9 | 10 | export { schema }; 11 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/issue/schema/schema1.ts: -------------------------------------------------------------------------------- 1 | import { gql, makeExecutableSchema } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | hello: String 6 | } 7 | `; 8 | const resolvers = { 9 | Query: { 10 | hello() { 11 | return 'Hello World'; 12 | }, 13 | }, 14 | }; 15 | const schema1 = makeExecutableSchema({ typeDefs, resolvers }); 16 | 17 | export { schema1 }; 18 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/issue/schema/schema2.ts: -------------------------------------------------------------------------------- 1 | import { gql, makeExecutableSchema } from 'apollo-server'; 2 | import { applyMiddleware } from 'graphql-middleware'; 3 | import { logCount } from '../middleware'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | miss: String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | miss() { 13 | return 'I miss her'; 14 | }, 15 | }, 16 | }; 17 | let schema2 = makeExecutableSchema({ typeDefs, resolvers }); 18 | schema2 = applyMiddleware(schema2, logCount); 19 | 20 | export { schema2 }; 21 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/issue/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { schema } from './schema'; 3 | 4 | const server = new ApolloServer({ schema }); 5 | const port = 3000; 6 | server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`)); 7 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/solution/middleware.ts: -------------------------------------------------------------------------------- 1 | const logCount = async (resolve, root, args, context, info) => { 2 | console.count('log middleware execute count'); 3 | const result = await resolve(root, args, context, info); 4 | return result; 5 | }; 6 | 7 | const logCountStandalone = { 8 | Query: { 9 | hello: async (resolve, parent, args, context, info) => { 10 | console.count('log middleware standalone execute count'); 11 | const result = await resolve(parent, args, context, info); 12 | return result; 13 | }, 14 | }, 15 | }; 16 | 17 | export { logCount, logCountStandalone }; 18 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/solution/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { mergeSchemas } from 'apollo-server'; 2 | import { schema1 } from './schema1'; 3 | import { schema2 } from './schema2'; 4 | import { applyMiddleware } from 'graphql-middleware'; 5 | import { logCountStandalone } from '../middleware'; 6 | 7 | let schema = mergeSchemas({ schemas: [schema1, schema2] }); 8 | schema = applyMiddleware(schema, logCountStandalone); 9 | 10 | export { schema }; 11 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/solution/schema/schema1.ts: -------------------------------------------------------------------------------- 1 | import { gql, makeExecutableSchema } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | hello: String 6 | } 7 | `; 8 | const resolvers = { 9 | Query: { 10 | hello() { 11 | return 'Hello World'; 12 | }, 13 | }, 14 | }; 15 | const schema1 = makeExecutableSchema({ typeDefs, resolvers }); 16 | 17 | export { schema1 }; 18 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/solution/schema/schema2.ts: -------------------------------------------------------------------------------- 1 | import { gql, makeExecutableSchema } from 'apollo-server'; 2 | import { applyMiddleware } from 'graphql-middleware'; 3 | import { logCount } from '../middleware'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | miss: String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | miss() { 13 | return 'I miss her'; 14 | }, 15 | }, 16 | }; 17 | let schema2 = makeExecutableSchema({ typeDefs, resolvers }); 18 | schema2 = applyMiddleware(schema2, logCount); 19 | 20 | export { schema2 }; 21 | -------------------------------------------------------------------------------- /src/apply-graphql-middleware-multiple-times/solution/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { schema } from './schema'; 3 | 4 | const server = new ApolloServer({ schema }); 5 | const port = 3000; 6 | server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`)); 7 | -------------------------------------------------------------------------------- /src/caching/01/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User 7 | 8 | 9 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/caching/01/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 4 | import faker from 'faker'; 5 | 6 | const typeDefs = gql` 7 | type User { 8 | name: String 9 | email: String 10 | } 11 | type Query { 12 | user: User 13 | } 14 | `; 15 | 16 | const resolvers = { 17 | Query: { 18 | user: async (_, __, context, { cacheControl }) => { 19 | console.count('resolve user'); 20 | cacheControl.setCacheHint({ maxAge: 40 }); 21 | const user = { name: faker.name.findName(), email: faker.internet.exampleEmail() }; 22 | return user; 23 | }, 24 | }, 25 | }; 26 | 27 | const app = express(); 28 | const port = 3001; 29 | const graphqlPath = '/graphql'; 30 | const server = new ApolloServer({ 31 | typeDefs, 32 | resolvers, 33 | tracing: true, 34 | // use LRU-memory cache by default 35 | plugins: [responseCachePlugin()], 36 | // set cache-control HTTP response header as expected 37 | // cacheControl: { 38 | // defaultMaxAge: 5, 39 | // }, 40 | // bug? DOES NOT set cache-control HTTP response header 41 | cacheControl: true, 42 | }); 43 | 44 | server.applyMiddleware({ app, path: graphqlPath }); 45 | 46 | app.get('/', (req, res) => { 47 | res.sendFile('index.html', { root: __dirname }); 48 | }); 49 | 50 | if (require.main === module) { 51 | app.listen(port, () => { 52 | console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`); 53 | }); 54 | } 55 | -------------------------------------------------------------------------------- /src/caching/02/graphql-server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | import { RedisCache } from 'apollo-server-cache-redis'; 4 | import faker from 'faker'; 5 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 6 | 7 | const typeDefs = gql` 8 | type User @cacheControl(maxAge: 30) { 9 | name: String 10 | email: String 11 | } 12 | type Query { 13 | user: User 14 | } 15 | `; 16 | 17 | const resolvers = { 18 | Query: { 19 | user: async () => { 20 | console.count('resolve user'); 21 | const user = { name: faker.name.findName(), email: faker.internet.exampleEmail() }; 22 | return user; 23 | }, 24 | }, 25 | }; 26 | 27 | const app = express(); 28 | const port = 3001; 29 | const graphqlPath = '/graphql'; 30 | const server = new ApolloServer({ 31 | typeDefs, 32 | resolvers, 33 | plugins: [responseCachePlugin()], 34 | cache: new RedisCache({ 35 | port: 6379, 36 | host: '127.0.0.1', 37 | family: 4, 38 | db: 0, 39 | }), 40 | }); 41 | 42 | server.applyMiddleware({ app, path: graphqlPath }); 43 | 44 | app.get('/', (req, res) => { 45 | res.sendFile('index.html', { root: __dirname }); 46 | }); 47 | 48 | if (require.main === module) { 49 | app.listen(port, () => { 50 | console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`); 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /src/caching/02/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User 7 | 8 | 9 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/caching/03/_mixins.js: -------------------------------------------------------------------------------- 1 | import low from 'lowdb'; 2 | import FileSync from 'lowdb/adapters/FileSync'; 3 | import path from 'path'; 4 | 5 | const source = path.resolve(__dirname, 'db.json'); 6 | const adapter = new FileSync(source); 7 | const db = low(adapter); 8 | 9 | db._.mixin({ 10 | getByIds: function(array, key, items) { 11 | return array.filter((row) => items.includes(row[key])); 12 | }, 13 | mget: function(store, keys) { 14 | return keys.map((k) => store[k]); 15 | }, 16 | }); 17 | 18 | export { db }; 19 | -------------------------------------------------------------------------------- /src/caching/03/_mixins.test.js: -------------------------------------------------------------------------------- 1 | import { db } from './_mixins'; 2 | import faker from 'faker'; 3 | 4 | describe('caching - _mixins', () => { 5 | beforeEach(() => { 6 | db.defaults({ 7 | users: [ 8 | { id: 1, name: faker.name.findName(), email: faker.internet.email() }, 9 | { id: 2, name: faker.name.findName(), email: faker.internet.email() }, 10 | { id: 3, name: faker.name.findName(), email: faker.internet.email() }, 11 | ], 12 | store: { 13 | 1: 'aaa', 14 | 2: { name: faker.name.findName() }, 15 | 3: [], 16 | }, 17 | }).write(); 18 | }); 19 | 20 | describe('#getByIds', () => { 21 | it('should pass', () => { 22 | const actual = db 23 | .get('users') 24 | .getByIds('id', [1, 2]) 25 | .value(); 26 | expect(actual).toHaveLength(2); 27 | expect(actual).toEqual( 28 | expect.arrayContaining([{ id: expect.any(Number), name: expect.any(String), email: expect.any(String) }]), 29 | ); 30 | }); 31 | }); 32 | 33 | describe('#mget', () => { 34 | it('should pass', () => { 35 | const actual = db 36 | .get('store') 37 | .mget([1, 2, 3]) 38 | .value(); 39 | expect(actual).toEqual(['aaa', { name: expect.any(String) }, []]); 40 | }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /src/caching/03/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "id": 1, 5 | "name": "Dr. Amya Schiller", 6 | "email": "Eda4@yahoo.com" 7 | }, 8 | { 9 | "id": 2, 10 | "name": "Consuelo Mohr", 11 | "email": "Adele_Ankunding99@yahoo.com" 12 | }, 13 | { 14 | "id": 3, 15 | "name": "Pete Gorczany", 16 | "email": "Geovanny_Satterfield@gmail.com" 17 | } 18 | ], 19 | "store": { 20 | "1": "aaa", 21 | "2": { 22 | "name": "Raegan Armstrong" 23 | }, 24 | "3": [] 25 | } 26 | } -------------------------------------------------------------------------------- /src/caching/03/lowdb-cache-dev.json: -------------------------------------------------------------------------------- 1 | { 2 | "store": { 3 | "fqc:ebc87a519483d4a02d43233c889a5cbe33f973b6ea93e27b92e554d8be94bac2": "{\"data\":{\"user\":{\"name\":\"Ethel Shields\",\"email\":\"Heber_Cummerata0@example.com\"}},\"cachePolicy\":{\"maxAge\":30,\"scope\":\"PUBLIC\"},\"cacheTime\":1587382272710}" 4 | } 5 | } -------------------------------------------------------------------------------- /src/caching/03/lowdb-cache-test.json: -------------------------------------------------------------------------------- 1 | { 2 | "store": { 3 | "1": "[1,2,3]" 4 | } 5 | } -------------------------------------------------------------------------------- /src/caching/03/lowdb-cache.test.ts: -------------------------------------------------------------------------------- 1 | import { TestableKeyValueCache } from 'apollo-server-caching'; 2 | import { LowdbCache, ILowdbOptions } from './lowdb-cache'; 3 | import path from 'path'; 4 | 5 | export function testKeyValueCache_Basics(keyValueCache: TestableKeyValueCache) { 6 | describe('basic cache functionality', () => { 7 | beforeEach(() => { 8 | if (keyValueCache.flush) { 9 | keyValueCache.flush(); 10 | } 11 | }); 12 | 13 | it('can do a basic get and set', async () => { 14 | await keyValueCache.set('hello', 'world'); 15 | expect(await keyValueCache.get('hello')).toBe('world'); 16 | expect(await keyValueCache.get('missing')).toBeUndefined(); 17 | }); 18 | 19 | it('can do a basic set and delete', async () => { 20 | await keyValueCache.set('hello', 'world'); 21 | expect(await keyValueCache.get('hello')).toBe('world'); 22 | await keyValueCache.delete('hello'); 23 | expect(await keyValueCache.get('hello')).toBeUndefined(); 24 | }); 25 | }); 26 | } 27 | 28 | const options: ILowdbOptions = { adapter: 'FileSync', source: path.resolve(__dirname, 'lowdb-cache-test.json') }; 29 | testKeyValueCache_Basics(new LowdbCache(options)); 30 | -------------------------------------------------------------------------------- /src/caching/03/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | import faker from 'faker'; 4 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 5 | import { LowdbCache } from './lowdb-cache'; 6 | import path from 'path'; 7 | 8 | const typeDefs = gql` 9 | type User @cacheControl(maxAge: 30) { 10 | name: String 11 | email: String 12 | } 13 | type Query { 14 | user: User 15 | } 16 | `; 17 | 18 | const resolvers = { 19 | Query: { 20 | user: async () => { 21 | console.count('resolve user'); 22 | const user = { name: faker.name.findName(), email: faker.internet.exampleEmail() }; 23 | return user; 24 | }, 25 | }, 26 | }; 27 | 28 | const app = express(); 29 | const port = 3001; 30 | const graphqlPath = '/graphql'; 31 | const server = new ApolloServer({ 32 | typeDefs, 33 | resolvers, 34 | plugins: [responseCachePlugin()], 35 | cache: new LowdbCache({ 36 | adapter: 'FileSync', 37 | source: path.resolve(__dirname, 'lowdb-cache-dev.json'), 38 | }), 39 | }); 40 | 41 | server.applyMiddleware({ app, path: graphqlPath }); 42 | 43 | if (require.main === module) { 44 | app.listen(port, () => { 45 | console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`); 46 | }); 47 | } 48 | -------------------------------------------------------------------------------- /src/client-batching-query/__tests__/__snapshots__/index.integration.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`client batching query mutation #addPost - multiple mutations 1`] = ` 4 | Object { 5 | "data": Object { 6 | "addPost1": Object { 7 | "code": 0, 8 | "message": "add post success", 9 | }, 10 | "addPost2": Object { 11 | "code": 0, 12 | "message": "add post success", 13 | }, 14 | }, 15 | "errors": undefined, 16 | "extensions": undefined, 17 | "http": Object { 18 | "headers": Headers { 19 | Symbol(map): Object {}, 20 | }, 21 | }, 22 | } 23 | `; 24 | 25 | exports[`client batching query mutation #addPost 1`] = ` 26 | Object { 27 | "data": Object { 28 | "addPost": Object { 29 | "code": 0, 30 | "message": "add post success", 31 | }, 32 | }, 33 | "errors": undefined, 34 | "extensions": undefined, 35 | "http": Object { 36 | "headers": Headers { 37 | Symbol(map): Object {}, 38 | }, 39 | }, 40 | } 41 | `; 42 | 43 | exports[`client batching query query #userById 1`] = ` 44 | Object { 45 | "data": Object { 46 | "userById": Object { 47 | "userEmail": "mrdulin@example.com", 48 | "userNme": "mrdulin", 49 | }, 50 | }, 51 | "errors": undefined, 52 | "extensions": undefined, 53 | "http": Object { 54 | "headers": Headers { 55 | Symbol(map): Object {}, 56 | }, 57 | }, 58 | } 59 | `; 60 | -------------------------------------------------------------------------------- /src/client-batching-query/__tests__/mockedDB.ts: -------------------------------------------------------------------------------- 1 | const db = { 2 | users: [ 3 | { 4 | userId: 1, 5 | userNme: 'mrdulin', 6 | userEmail: 'mrdulin@example.com', 7 | }, 8 | ], 9 | posts: [ 10 | { 11 | postId: 1, 12 | postTitle: 'jest', 13 | postContent: 'jest content', 14 | postAuthorId: 1, 15 | }, 16 | ], 17 | }; 18 | 19 | export { db }; 20 | -------------------------------------------------------------------------------- /src/client-batching-query/db/memoryDB.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | const users = [ 3 | { 4 | userId: 1, 5 | userNme: faker.name.findName(), 6 | userEmail: faker.internet.email(), 7 | }, 8 | { 9 | userId: 2, 10 | userNme: faker.name.findName(), 11 | userEmail: faker.internet.email(), 12 | }, 13 | { 14 | userId: 3, 15 | userNme: faker.name.findName(), 16 | userEmail: faker.internet.email(), 17 | }, 18 | ]; 19 | 20 | const posts = [ 21 | { 22 | postId: faker.random.uuid(), 23 | postTitle: faker.lorem.sentence(), 24 | postContent: faker.lorem.paragraphs(), 25 | postAuthorId: 1, 26 | }, 27 | { 28 | postId: faker.random.uuid(), 29 | postTitle: faker.lorem.sentence(), 30 | postContent: faker.lorem.paragraphs(), 31 | postAuthorId: 2, 32 | }, 33 | { 34 | postId: faker.random.uuid(), 35 | postTitle: faker.lorem.sentence(), 36 | postContent: faker.lorem.paragraphs(), 37 | postAuthorId: 1, 38 | }, 39 | ]; 40 | 41 | const memoryDB = { 42 | users, 43 | posts, 44 | }; 45 | 46 | export { memoryDB }; 47 | -------------------------------------------------------------------------------- /src/client-batching-query/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | 3 | (async function main() { 4 | await createApolloServer(); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/client-batching-query/resolver.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | import { IResolvers } from 'apollo-server'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | posts: (_, __, { db }) => { 7 | return db.posts; 8 | }, 9 | userById: (_, { id }, { db }) => { 10 | console.count('Query.userById'); 11 | const user = db.users.find((u) => u.userId.toString() === id); 12 | return user; 13 | }, 14 | }, 15 | Post: { 16 | postAuthor: (parent, _, { db }) => { 17 | return db.users.find((user) => user.userId.toString() === parent.postAuthorId.toString()); 18 | }, 19 | }, 20 | 21 | Mutation: { 22 | addPost: (_, { post }, { db }) => { 23 | return new Promise((resolve) => { 24 | setTimeout(() => { 25 | const newPost = Object.assign({}, post, { postId: faker.random.uuid() }); 26 | db.posts.push(newPost); 27 | resolve({ code: 0, message: 'add post success' }); 28 | }, 3000); 29 | }); 30 | }, 31 | }, 32 | }; 33 | export { resolvers }; 34 | -------------------------------------------------------------------------------- /src/client-batching-query/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolver'; 4 | import { memoryDB } from './db/memoryDB'; 5 | import { ApolloServerBase } from 'apollo-server-core'; 6 | 7 | async function createApolloServer(): Promise { 8 | const PORT = process.env.PORT || 3000; 9 | const server = new ApolloServer({ typeDefs, resolvers, context: { db: memoryDB } }); 10 | 11 | await server 12 | .listen(PORT) 13 | .then(({ url }: ServerInfo) => { 14 | console.log(`🚀 Server ready at ${url}`); 15 | }) 16 | .catch((error) => { 17 | console.error('Create server failed.'); 18 | console.error(error); 19 | }); 20 | return server; 21 | } 22 | 23 | export { createApolloServer }; 24 | -------------------------------------------------------------------------------- /src/client-batching-query/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Post { 5 | postId: ID! 6 | postTitle: String! 7 | postContent: String! 8 | postAuthorId: ID 9 | postAuthor: User 10 | } 11 | 12 | input PostInput { 13 | postTitle: String! 14 | postContent: String! 15 | postAuthorId: ID! 16 | } 17 | 18 | type User { 19 | userId: ID! 20 | userNme: String! 21 | userEmail: String! 22 | } 23 | 24 | type CommonResponse { 25 | code: Int! 26 | message: String! 27 | } 28 | 29 | type Query { 30 | posts: [Post]! 31 | userById(id: ID!): User 32 | } 33 | 34 | type Mutation { 35 | addPost(post: PostInput): CommonResponse! 36 | } 37 | `; 38 | 39 | export { typeDefs }; 40 | -------------------------------------------------------------------------------- /src/custom-http-status-code/server.ts: -------------------------------------------------------------------------------- 1 | import { graphqlExpress } from './expressApollo'; 2 | import express from 'express'; 3 | import { makeExecutableSchema } from 'graphql-tools'; 4 | import { gql } from 'apollo-server'; 5 | 6 | const app = express(); 7 | const port = 3000; 8 | const graphqlEndpoint = '/graphql'; 9 | 10 | const typeDefs = gql` 11 | type Query { 12 | _: String 13 | } 14 | `; 15 | const resolvers = { 16 | Query: { 17 | _: () => { 18 | throw new Error('some error'); 19 | }, 20 | }, 21 | }; 22 | const schema = makeExecutableSchema({ typeDefs, resolvers }); 23 | 24 | app.use(express.json()); 25 | app.use(graphqlEndpoint, graphqlExpress({ schema })); 26 | 27 | app.use((error, req, res, next) => { 28 | if (error === 'some error') { 29 | return res.status(400).send({ message: error.message ? error.message : error }); 30 | } 31 | res.status(500); 32 | res.render('error', { error }); 33 | }); 34 | 35 | app.listen(port, () => { 36 | console.log(`Http server is listening on http://localhost:${port}${graphqlEndpoint}`); 37 | }); 38 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/appContext.ts: -------------------------------------------------------------------------------- 1 | import { db } from './db'; 2 | 3 | export interface IAppContext { 4 | db: typeof db; 5 | } 6 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/db.ts: -------------------------------------------------------------------------------- 1 | enum Device { 2 | UNKNOWN = 'Other', 3 | DESKTOP = 'Computers', 4 | HIGH_END_MOBILE = 'Mobile devices with full browsers', 5 | TABLET = 'Tablets with full browsers', 6 | CONNECTED_TV = 'Devices streaming video content to TV screens', 7 | } 8 | 9 | export const db = { 10 | campaignPerformanceReports: [ 11 | { 12 | campaignId: 1, 13 | campaignNme: 'test', 14 | device: Device.DESKTOP, 15 | }, 16 | ], 17 | }; 18 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IAppContext } from './appContext'; 2 | import { IResolvers } from 'apollo-server'; 3 | 4 | export const resolvers: IResolvers = { 5 | Device: { 6 | UNKNOWN: 'Other', 7 | DESKTOP: 'Computers', 8 | HIGH_END_MOBILE: 'Mobile devices with full browsers', 9 | TABLET: 'Tablets with full browsers', 10 | CONNECTED_TV: 'Devices streaming video content to TV screens', 11 | }, 12 | 13 | Query: { 14 | async campaignPerformanceReports(_, __, { db }: IAppContext) { 15 | return db.campaignPerformanceReports; 16 | }, 17 | // https://stackoverflow.com/questions/58394659/cant-custom-value-of-graphql-enum#58396460 18 | async someQuery(_, { device }) { 19 | console.log(`device=${device}`); 20 | return device; 21 | }, 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/schema.ts: -------------------------------------------------------------------------------- 1 | import { typeDefs } from './typeDefs'; 2 | import { resolvers } from './resolvers'; 3 | import { makeExecutableSchema } from 'graphql-tools'; 4 | 5 | export const schema = makeExecutableSchema({ typeDefs, resolvers }); 6 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolvers'; 4 | import { db } from './db'; 5 | 6 | const PORT = process.env.PORT || 3000; 7 | const server = new ApolloServer({ typeDefs, resolvers, context: { db } }); 8 | 9 | server 10 | .listen(PORT) 11 | .then(({ url }: ServerInfo) => { 12 | console.log(`🚀 Server ready at ${url}`); 13 | }) 14 | .catch((error) => { 15 | console.error('Create server failed.'); 16 | console.error(error); 17 | }); 18 | -------------------------------------------------------------------------------- /src/custom-scalar-and-enum/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | export const typeDefs = gql` 4 | enum Device { 5 | UNKNOWN 6 | DESKTOP 7 | HIGH_END_MOBILE 8 | TABLET 9 | CONNECTED_TV 10 | } 11 | 12 | type CampaignPerformanceReport { 13 | campaignNme: String! 14 | campaignId: ID! 15 | device: Device 16 | } 17 | 18 | type Query { 19 | campaignPerformanceReports: [CampaignPerformanceReport]! 20 | someQuery(device: Device!): Device! 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/dataloader/__tests__/example.benchmark.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | import Benchmark, { Suite } from 'benchmark'; 3 | import DataLoader from 'dataloader'; 4 | 5 | interface IUser { 6 | id: number; 7 | email: string; 8 | } 9 | interface IDB { 10 | users: IUser[]; 11 | } 12 | const DB: IDB = { 13 | users: [], 14 | }; 15 | 16 | for (let i = 0; i < 10000; i++) { 17 | DB.users.push({ id: i + 1, email: faker.internet.email() }); 18 | } 19 | 20 | async function findUserById(id: number) { 21 | return DB.users.find((u) => u.id === id); 22 | } 23 | 24 | async function findByUserIds(ids: number[]) { 25 | return DB.users.filter((v) => ids.includes(v.id)); 26 | } 27 | const userLoader = new DataLoader(findByUserIds); 28 | 29 | const suite: Suite = new Benchmark.Suite('dataloader'); 30 | 31 | suite 32 | .add('find user by id without dataloader', { 33 | defer: true, 34 | fn: async (deferred) => { 35 | const user = await findUserById(100); 36 | console.log(user); 37 | deferred.resolve(); 38 | }, 39 | }) 40 | .add('find user by id with dataloader', { 41 | defer: true, 42 | fn: async (deferred) => { 43 | const user = await userLoader.load(101); 44 | console.log(user); 45 | deferred.resolve(); 46 | }, 47 | }) 48 | .on('complete', function(this: any) { 49 | this.forEach((t) => { 50 | console.log(t.toString()); 51 | }); 52 | console.log('Fastest is ' + this.filter('fastest').map('name')); 53 | }) 54 | .run({ async: true }); 55 | -------------------------------------------------------------------------------- /src/dataloader/db.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | 3 | interface IUser { 4 | id: number; 5 | name: string; 6 | email: string; 7 | } 8 | 9 | interface IPost { 10 | id: number; 11 | title: string; 12 | content: string; 13 | userId: number; 14 | } 15 | 16 | const users: IUser[] = [ 17 | { id: 1, name: 'a', email: 'a@gmail.com' }, 18 | { id: 2, name: 'b', email: 'b@gmail.com' }, 19 | { id: 3, name: 'c', email: 'c@gmail.com' }, 20 | ]; 21 | 22 | const posts: IPost[] = [ 23 | { id: 1, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), userId: 1 }, 24 | { id: 2, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), userId: 1 }, 25 | { id: 3, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), userId: 1 }, 26 | ]; 27 | 28 | const db = { users, posts }; 29 | export { db, IUser, IPost }; 30 | -------------------------------------------------------------------------------- /src/dataloader/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | 3 | (async function main() { 4 | await createApolloServer(); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/dataloader/modules/user/index.ts: -------------------------------------------------------------------------------- 1 | export * from './user'; 2 | export * from './userLoader'; 3 | -------------------------------------------------------------------------------- /src/dataloader/modules/user/user.ts: -------------------------------------------------------------------------------- 1 | import { db, IUser } from '../../db'; 2 | import _ from 'lodash'; 3 | 4 | const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); 5 | 6 | const User = { 7 | findByIds: async (ids: number[]): Promise> => { 8 | const dataSet = ids 9 | .map((id) => { 10 | const userFound = db.users.find((user) => user.id.toString() === id.toString()); 11 | if (userFound) { 12 | return { ...userFound }; 13 | } 14 | }) 15 | // make recordSet order random for simulating real database 16 | .sort((a, b) => { 17 | if (a && b) { 18 | return b.id - a.id; 19 | } 20 | return 0; 21 | }); 22 | 23 | const uniqDataSet = _.uniqBy(dataSet, 'id'); 24 | 25 | return Promise.all(uniqDataSet); 26 | }, 27 | 28 | findById: (id: number) => { 29 | console.count('User.findById'); 30 | return db.users.find((user) => user.id.toString() === id.toString()); 31 | }, 32 | }; 33 | 34 | export { User }; 35 | -------------------------------------------------------------------------------- /src/dataloader/modules/user/userLoader.ts: -------------------------------------------------------------------------------- 1 | import Dataloader from 'dataloader'; 2 | import { User } from './user'; 3 | import { IUser } from '../../db'; 4 | import _ from 'lodash'; 5 | 6 | const UserLoader = { 7 | findByIds: (options?: Dataloader.Options) => { 8 | const loadCalls: any[] = []; 9 | const cacheMap = new Map(); 10 | const loader = new Dataloader((keys: number[]) => { 11 | loadCalls.push(keys); 12 | return User.findByIds(keys) 13 | .then((dataSet: Array) => { 14 | console.log(`dataSet before sort and map result to key : ${JSON.stringify(dataSet)}`); 15 | return dataSet; 16 | }) 17 | .then( 18 | (dataSet: Array): Array => 19 | keys.map((key: number) => _.keyBy(dataSet, 'id')[key]), 20 | ) 21 | .then((dataSet) => { 22 | console.log(`dataSet after sort and map result to key : ${JSON.stringify(dataSet)}`); 23 | return dataSet; 24 | }); 25 | }, options); 26 | 27 | function getCache() { 28 | const cache = {}; 29 | for (const [key, val] of cacheMap.entries()) { 30 | cache[key] = val; 31 | } 32 | return cache; 33 | } 34 | 35 | return { loader, loadCalls, getCache }; 36 | }, 37 | }; 38 | 39 | export { UserLoader }; 40 | -------------------------------------------------------------------------------- /src/dataloader/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | 3 | const resolvers: IResolvers = { 4 | Query: { 5 | users: (_, __, { db }) => { 6 | return db.users; 7 | }, 8 | 9 | post: (_, { id }, { db }) => { 10 | return db.posts.find((post) => post.id.toString() === id); 11 | }, 12 | 13 | posts: (_, __, { db }) => { 14 | return db.posts; 15 | }, 16 | }, 17 | 18 | Post: { 19 | author: (post, _, { db, UserLoader, User }) => { 20 | // return User.findById(post.userId); 21 | return UserLoader.findByIds.load(post.userId); 22 | }, 23 | authorName1: (post, _, { UserLoader }) => { 24 | return UserLoader.findByIds 25 | .load(post.userId) 26 | .then(() => UserLoader.findByIds.load(post.userId)) 27 | .then((user) => user.name); 28 | }, 29 | authorName2: (post, _, { UserLoader }) => { 30 | return UserLoader.findByIds 31 | .load(post.userId) 32 | .then(() => UserLoader.findByIds.load(post.userId)) 33 | .then((user) => user.name); 34 | }, 35 | authorName3: (post, _, { UserLoader }) => { 36 | return UserLoader.findByIds 37 | .load(post.userId) 38 | .then(() => UserLoader.findByIds.load(post.userId)) 39 | .then((user) => user.name); 40 | }, 41 | }, 42 | }; 43 | 44 | export { resolvers }; 45 | -------------------------------------------------------------------------------- /src/dataloader/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolvers'; 4 | import { db } from './db'; 5 | import { ApolloServerBase } from 'apollo-server-core'; 6 | import { UserLoader, User } from './modules/user'; 7 | 8 | async function createApolloServer(): Promise { 9 | const PORT = process.env.PORT || 3000; 10 | const server = new ApolloServer({ typeDefs, resolvers, context: { db, UserLoader, User } }); 11 | 12 | await server 13 | .listen(PORT) 14 | .then(({ url }: ServerInfo) => { 15 | console.log(`🚀 Server ready at ${url}`); 16 | }) 17 | .catch((error) => { 18 | console.error('Create server failed.'); 19 | console.error(error); 20 | }); 21 | return server; 22 | } 23 | 24 | export { createApolloServer }; 25 | -------------------------------------------------------------------------------- /src/dataloader/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | id: ID! 6 | name: String! 7 | email: String! 8 | } 9 | 10 | type Post { 11 | id: ID! 12 | title: String! 13 | content: String! 14 | author: User 15 | 16 | # == For testing dataloader which disabled cache == 17 | authorName1: String 18 | authorName2: String 19 | authorName3: String 20 | } 21 | type Query { 22 | users: [User]! 23 | post(id: ID!): Post 24 | posts: [Post]! 25 | } 26 | `; 27 | 28 | export { typeDefs }; 29 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-1/db.ts: -------------------------------------------------------------------------------- 1 | const db = { 2 | posts: [], 3 | }; 4 | 5 | export { db }; 6 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-1/main.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolver'; 4 | import { db } from './db'; 5 | 6 | const PORT = process.env.PORT || 3000; 7 | const server = new ApolloServer({ typeDefs, resolvers, context: { db } }); 8 | 9 | server 10 | .listen(PORT) 11 | .then(({ url }: ServerInfo) => { 12 | console.log(`🚀 Server ready at ${url}`); 13 | }) 14 | .catch((error) => { 15 | console.error('Create server failed.'); 16 | console.error(error); 17 | }); 18 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-1/resolver.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'graphql-tools'; 2 | import faker from 'faker'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | posts: (_, __, { db }) => { 7 | return db.posts; 8 | }, 9 | }, 10 | 11 | Mutation: { 12 | addPost: (_, { post }, { db }) => { 13 | return new Promise((resolve) => { 14 | setTimeout(() => { 15 | const newPost = Object.assign({}, post, { postId: faker.random.uuid() }); 16 | db.posts.push(newPost); 17 | resolve({ code: 0, message: 'add post success' }); 18 | }, 3000); 19 | }); 20 | }, 21 | }, 22 | }; 23 | export { resolvers }; 24 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-1/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Post { 5 | postId: ID! 6 | postTitle: String! 7 | postContent: String! 8 | postAuthorId: ID 9 | } 10 | 11 | input PostTag { 12 | name: String! 13 | } 14 | 15 | input PostInput { 16 | postTitle: String! 17 | postContent: String! 18 | postAuthorId: ID! 19 | postTags: [PostTag!]! 20 | } 21 | 22 | type CommonResponse { 23 | code: Int! 24 | message: String! 25 | } 26 | 27 | type Query { 28 | posts: [Post]! 29 | } 30 | 31 | type Mutation { 32 | addPost(post: PostInput): CommonResponse! 33 | } 34 | `; 35 | 36 | export { typeDefs }; 37 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-2/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | (async function main() { 3 | await createApolloServer(); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-2/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo, introspectSchema, makeRemoteExecutableSchema, mergeSchemas } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { ApolloServerBase } from 'apollo-server-core'; 4 | import { HttpLink } from 'apollo-link-http'; 5 | import fetch from 'node-fetch'; 6 | import { printSchema } from 'graphql'; 7 | 8 | // TODO: https://github.com/apollographql/apollo-link/issues/513 9 | const link = new HttpLink({ uri: 'http://localhost:3000', fetch: fetch as any }); 10 | 11 | async function createApolloServer(): Promise { 12 | const PORT = process.env.PORT || 3001; 13 | 14 | const graphqlSchema = await introspectSchema(link); 15 | const remoteExecutableSchema = makeRemoteExecutableSchema({ schema: graphqlSchema, link }); 16 | const schema = mergeSchemas({ schemas: [remoteExecutableSchema, typeDefs] }); 17 | console.log(printSchema(schema)); 18 | 19 | const server = new ApolloServer({ schema }); 20 | await server 21 | .listen(PORT) 22 | .then(({ url }: ServerInfo) => { 23 | console.log(`🚀 Server ready at ${url}`); 24 | }) 25 | .catch((error) => { 26 | console.error('Create server failed.'); 27 | console.error(error); 28 | }); 29 | return server; 30 | } 31 | 32 | export { createApolloServer }; 33 | -------------------------------------------------------------------------------- /src/extend-remote-schema/service-2/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | extend input PostTag { 5 | color: String 6 | } 7 | `; 8 | 9 | export { typeDefs }; 10 | -------------------------------------------------------------------------------- /src/federation/common/db.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | 3 | const MemoryDB = { 4 | users: [ 5 | { id: 1, name: faker.name.findName() }, 6 | { id: 2, name: faker.name.findName() }, 7 | ], 8 | products: [ 9 | { upc: 1, name: faker.commerce.product(), price: faker.commerce.price() }, 10 | { upc: 2, name: faker.commerce.product(), price: faker.commerce.price() }, 11 | { upc: 3, name: faker.commerce.product(), price: faker.commerce.price() }, 12 | ], 13 | reviews: [ 14 | { id: 1, product_upc: 3 }, 15 | { id: 2, product_upc: 2 }, 16 | { id: 3, product_upc: 1 }, 17 | { id: 4, product_upc: 1 }, 18 | ], 19 | }; 20 | 21 | export { MemoryDB }; 22 | -------------------------------------------------------------------------------- /src/federation/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './db'; 2 | -------------------------------------------------------------------------------- /src/federation/gateway/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { ApolloGateway, RemoteGraphQLDataSource } from '@apollo/gateway'; 3 | 4 | class AuthenticatedDataSource extends RemoteGraphQLDataSource { 5 | public willSendRequest({ request, context }) { 6 | request.http.headers.set('user_id', context.userId); 7 | } 8 | } 9 | 10 | function getUserId(token: string): number { 11 | return token ? 1 : -1; 12 | } 13 | 14 | const gateway = new ApolloGateway({ 15 | serviceList: [ 16 | { name: 'user', url: 'http://localhost:4001' }, 17 | { name: 'product', url: 'http://localhost:4002' }, 18 | { name: 'review', url: 'http://localhost:4003' }, 19 | ], 20 | serviceHealthCheck: true, 21 | buildService({ name, url }) { 22 | return new AuthenticatedDataSource({ url }); 23 | }, 24 | }); 25 | 26 | const server = new ApolloServer({ 27 | gateway, 28 | subscriptions: false, 29 | context: ({ req }) => { 30 | const token = req.headers.authorization || ''; 31 | console.log('token:', token); 32 | const userId = getUserId(token); 33 | return { userId }; 34 | }, 35 | }); 36 | 37 | server.listen().then(({ url }) => { 38 | console.log(`🚀 Server ready at ${url}`); 39 | }); 40 | -------------------------------------------------------------------------------- /src/federation/productService/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { buildFederatedSchema } from '@apollo/federation'; 3 | import { MemoryDB } from '../common'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | productById(upc: ID!): Product 8 | } 9 | type Product @key(fields: "upc") { 10 | upc: ID! 11 | name: String! 12 | price: Int 13 | } 14 | `; 15 | 16 | const resolvers: any = { 17 | Query: { 18 | productById: (_, { upc }, { db }) => { 19 | return db.products.find((p) => p.upc === +upc); 20 | }, 21 | }, 22 | Product: { 23 | __resolveReference(product, { db }) { 24 | console.log(`__resolveReference, product: ${JSON.stringify(product)}`); 25 | return db.products.find((p) => p.upc === +product.upc); 26 | }, 27 | }, 28 | }; 29 | 30 | const server = new ApolloServer({ 31 | schema: buildFederatedSchema([{ typeDefs, resolvers }]), 32 | context: ({ req }) => { 33 | const userId = req.headers.user_id || ''; 34 | console.log('userId: ', userId); 35 | return { 36 | db: MemoryDB, 37 | }; 38 | }, 39 | playground: true, 40 | introspection: true, 41 | }); 42 | const port = 4002; 43 | server.listen(port).then(({ url }) => console.log(`product service server is listening on ${url}graphql`)); 44 | -------------------------------------------------------------------------------- /src/federation/reviewService/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { buildFederatedSchema } from '@apollo/federation'; 3 | import { MemoryDB } from '../common'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | reviewById(id: ID!): Review 8 | } 9 | type Review { 10 | id: ID! 11 | product: Product 12 | } 13 | extend type Product @key(fields: "upc") { 14 | upc: ID! @external 15 | reviews: [Review]! 16 | } 17 | `; 18 | 19 | const resolvers: any = { 20 | Query: { 21 | reviewById: (_, { id }, { db }) => { 22 | return db.reviews.find((r) => r.id === +id); 23 | }, 24 | }, 25 | Review: { 26 | product(review) { 27 | console.log('resolve Review.product, review:', review); 28 | return { __typename: 'Product', upc: review.product_upc }; 29 | }, 30 | }, 31 | Product: { 32 | reviews(product, _, { db }) { 33 | console.log('resolve Product.reviews, product: ', product, 'db: ', db); 34 | return db.reviews.filter((r) => r.product_upc === +product.upc) || []; 35 | }, 36 | }, 37 | }; 38 | 39 | const server = new ApolloServer({ 40 | schema: buildFederatedSchema([{ typeDefs, resolvers }]), 41 | context: ({ req }) => { 42 | const userId = req.headers.user_id || ''; 43 | console.log('userId: ', userId); 44 | return { 45 | db: MemoryDB, 46 | }; 47 | }, 48 | playground: true, 49 | introspection: true, 50 | }); 51 | const port = 4003; 52 | server.listen(port).then(({ url }) => console.log(`review service server is listening on ${url}graphql`)); 53 | -------------------------------------------------------------------------------- /src/federation/userService/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { buildFederatedSchema } from '@apollo/federation'; 3 | import { MemoryDB } from '../common'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | userById(id: ID!): User 8 | } 9 | type User @key(fields: "id") { 10 | id: ID! 11 | name: String! 12 | } 13 | `; 14 | 15 | const resolvers: any = { 16 | Query: { 17 | userById: (_, { id }, { db }) => { 18 | return db.users.find((u) => u.id === +id); 19 | }, 20 | }, 21 | User: { 22 | __resolveReference(user, { db }) { 23 | console.log(`__resolveReference, user: ${user.toJSON()}`); 24 | return db.users.find((u) => u.id === user.id); 25 | }, 26 | }, 27 | }; 28 | 29 | const server = new ApolloServer({ 30 | schema: buildFederatedSchema([{ typeDefs, resolvers }]), 31 | context: ({ req }) => { 32 | const userId = req.headers.user_id || ''; 33 | console.log('userId: ', userId); 34 | return { 35 | db: MemoryDB, 36 | }; 37 | }, 38 | playground: true, 39 | introspection: true, 40 | }); 41 | const port = 4001; 42 | server.listen(port).then(({ url }) => console.log(`user service server is listening on ${url}graphql`)); 43 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/config/env.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { logger } from '../utils'; 3 | 4 | if (process.env.NODE_ENV !== 'production') { 5 | // tslint:disable-next-line: no-var-requires 6 | const dotenvConfigOutput = require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 7 | if (dotenvConfigOutput.error) { 8 | logger.error(dotenvConfigOutput.error); 9 | process.exit(1); 10 | } 11 | logger.debug(`dotenvConfigOutput: `, { arguments: { dotenvConfigOutput } }); 12 | } 13 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/db/knex.ts: -------------------------------------------------------------------------------- 1 | import knex from 'knex'; 2 | import humps from 'humps'; 3 | import { logger } from '../utils'; 4 | 5 | import '../config/env'; 6 | 7 | const connection = { 8 | host: process.env.SQL_HOST || '127.0.0.1', 9 | port: Number.parseInt(process.env.SQL_PORT || '5432', 10), 10 | database: process.env.SQL_DATABASE || 'postgres', 11 | user: process.env.SQL_USER || 'postgres', 12 | password: process.env.SQL_PASSWORD || '', 13 | ssl: process.env.SQL_SSL === 'true' ? true : false, 14 | }; 15 | logger.debug('db connection config: ', { arguments: { connection } }); 16 | 17 | const config: knex.Config = { 18 | client: 'pg', 19 | connection, 20 | pool: { 21 | min: 2, 22 | max: 10, 23 | }, 24 | debug: process.env.NODE_ENV !== 'production', 25 | wrapIdentifier: (value, origImpl, queryContext) => { 26 | logger.debug(`[wrapIdentifier] value = ${value}`); 27 | const identifier = origImpl(humps.decamelize(value)); 28 | logger.debug(`[wrapIdentifier] identifier = ${identifier}`); 29 | return identifier; 30 | }, 31 | postProcessResponse: (result, queryContext) => { 32 | logger.debug(result, { context: 'postProcessResponse' }); 33 | if (result.rows) { 34 | return humps.camelizeKeys(result); 35 | } 36 | return humps.camelizeKeys(result); 37 | }, 38 | }; 39 | 40 | const Knex = knex(config); 41 | 42 | export { Knex as knex }; 43 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/db/knexfile.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | 3 | if (process.env.NODE_ENV !== 'production') { 4 | // tslint:disable-next-line: no-var-requires 5 | const dotenvConfigOutput = require('dotenv').config({ path: path.resolve(__dirname, '../.env') }); 6 | if (dotenvConfigOutput.error) { 7 | console.error(dotenvConfigOutput.error); 8 | process.exit(1); 9 | } 10 | console.log(`dotenvConfigOutput: `, JSON.stringify(dotenvConfigOutput.parsed, null, 2)); 11 | } 12 | 13 | module.exports = { 14 | development: { 15 | client: 'postgresql', 16 | connection: { 17 | host: process.env.SQL_HOST || '127.0.0.1', 18 | port: Number.parseInt(process.env.SQL_PORT || '5432', 10), 19 | database: process.env.SQL_DATABASE || 'postgres', 20 | user: process.env.SQL_USER || 'postgres', 21 | password: process.env.SQL_PASSWORD || '', 22 | ssl: process.env.SQL_SSL === 'true' ? true : false, 23 | }, 24 | pool: { 25 | min: 2, 26 | max: 10, 27 | }, 28 | migrations: { 29 | tableName: 'knex_migrations', 30 | extension: 'ts', 31 | directory: './migrations', 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/db/migrate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npx knex migrate:latest --cwd ./db -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/db/rollback.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npx knex migrate:rollback --cwd ./db -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/db/seed.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | npx knex seed:run --cwd ./db -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/interfaces/apollo-server/context.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | import { IPostLoader, IUserLoader } from '../../modules'; 3 | 4 | interface IAppContext { 5 | knex: Knex; 6 | PostLoader: IPostLoader; 7 | UserLoader: IUserLoader; 8 | } 9 | 10 | export { IAppContext }; 11 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/interfaces/common/types.ts: -------------------------------------------------------------------------------- 1 | export type ID = string | number; 2 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/interfaces/modules/post/postInput.ts: -------------------------------------------------------------------------------- 1 | import { ID } from '../../common/types'; 2 | import { ITagEntity } from '../tag/tag'; 3 | 4 | interface IPostInput { 5 | postTitle: string; 6 | postContent: string; 7 | postTags?: ITagEntity[]; 8 | postAuthorId: ID; 9 | } 10 | 11 | export { IPostInput }; 12 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/interfaces/modules/tag/tag.ts: -------------------------------------------------------------------------------- 1 | import { ID } from '../../common/types'; 2 | 3 | interface ITagEntity { 4 | tagId: ID; 5 | tagNme: string; 6 | } 7 | 8 | export { ITagEntity }; 9 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | import './config/env'; 3 | 4 | (async function main() { 5 | await createApolloServer().catch(console.error); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/modules/User.ts: -------------------------------------------------------------------------------- 1 | import { ID } from '../interfaces/common/types'; 2 | import { mapToBindings } from '../utils'; 3 | import { knex } from '../db/knex'; 4 | import Dataloader from 'dataloader'; 5 | 6 | const User = { 7 | async findByIds(ids: ID[]) { 8 | const sql = ` 9 | select 10 | * 11 | from users 12 | where user_id in (${mapToBindings(ids)}) 13 | `; 14 | return knex.raw(sql, ids).then(({ rows }) => rows); 15 | }, 16 | }; 17 | 18 | interface IUserLoader { 19 | userFriends: Dataloader; 20 | } 21 | 22 | const UserLoader: IUserLoader = { 23 | userFriends: new Dataloader((keys) => 24 | User.findByIds(keys).then((rs) => { 25 | return keys.map((key) => rs.find((r) => r.userId === key)); 26 | }), 27 | ), 28 | }; 29 | 30 | export { User, UserLoader, IUserLoader }; 31 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Post'; 2 | export * from './User'; 3 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolvers'; 4 | import { knex } from './db/knex'; 5 | import { logger } from './utils'; 6 | import { PostLoader, UserLoader } from './modules'; 7 | 8 | function createApolloServer(): Promise { 9 | const PORT = process.env.PORT || 3000; 10 | 11 | const apolloServer = new ApolloServer({ 12 | typeDefs, 13 | resolvers, 14 | context: { knex, PostLoader, UserLoader }, 15 | engine: { 16 | apiKey: process.env.APOLLO_ENGINE_API_KEY, 17 | schemaTag: process.env.ENGINE_SCHEMA_TAG, 18 | }, 19 | }); 20 | 21 | return new Promise((resolve, reject) => { 22 | apolloServer 23 | .listen(PORT) 24 | .then(({ url }: ServerInfo) => { 25 | logger.info(`🚀 Server ready at ${url}`); 26 | resolve(apolloServer); 27 | }) 28 | .catch((error) => { 29 | console.error(error); 30 | reject('Create server failed.'); 31 | }); 32 | }); 33 | } 34 | 35 | export { createApolloServer }; 36 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | userId: ID! 6 | userNme: String 7 | userEmail: String! 8 | userFullNme: String! 9 | userPosts: [Post]! 10 | userFriends: [User]! 11 | } 12 | 13 | type Post { 14 | postId: ID! 15 | postTitle: String! 16 | postContent: String! 17 | postCreatedAt: String 18 | postUpdatedAt: String 19 | postTags: [Tag]! 20 | postAuthor: User 21 | } 22 | 23 | input PostInput { 24 | postTitle: String! 25 | postContent: String! 26 | postTags: [TagInput] 27 | postAuthorId: ID! 28 | } 29 | 30 | input TagInput { 31 | tagNme: String! 32 | } 33 | 34 | type Tag { 35 | tagId: ID! 36 | tagNme: String! 37 | } 38 | 39 | type CommonResponse { 40 | code: Int! 41 | message: String! 42 | } 43 | 44 | type Query { 45 | user(id: ID!): User 46 | post(id: ID!): Post 47 | posts(ids: [ID!]): [Post]! 48 | } 49 | 50 | type Mutation { 51 | post(postInput: PostInput!): CommonResponse 52 | } 53 | `; 54 | 55 | export { typeDefs }; 56 | -------------------------------------------------------------------------------- /src/fields-conversion-with-knex/utils.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from 'dl-toolkits'; 2 | 3 | const logger = createLogger(); 4 | 5 | function mapToBindings(parameters: any[]) { 6 | return parameters.map(() => '?').join(','); 7 | } 8 | 9 | export { logger, mapToBindings }; 10 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/__tests__/contexts.ts: -------------------------------------------------------------------------------- 1 | import { Role, IDB } from '../db'; 2 | 3 | const dbMocked: IDB = { 4 | users: [ 5 | { 6 | id: 1, 7 | name: 'mrdulin', 8 | email: 'mrdulin@example.com', 9 | role: Role.viewer, 10 | bitcoinAddress: 'sxsdfr', 11 | }, 12 | { 13 | id: 2, 14 | name: 'elsa', 15 | email: 'elsa@example.com', 16 | role: Role.admin, 17 | bitcoinAddress: 'sxsdfr', 18 | }, 19 | ], 20 | posts: [ 21 | { 22 | id: 1, 23 | title: 'ts', 24 | content: 'ts content', 25 | authorId: 1, 26 | }, 27 | ], 28 | }; 29 | 30 | export const context0 = { db: dbMocked, req: {} }; 31 | export const context1 = { db: dbMocked, req: { user: { role: Role.viewer } } }; 32 | export const context2 = { db: dbMocked, req: { user: { role: Role.editor } } }; 33 | export const context3 = { db: dbMocked, req: { user: { role: Role.admin } } }; 34 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/__tests__/fragments.ts: -------------------------------------------------------------------------------- 1 | export const userBaseInfo = ` 2 | fragment UserBaseInfo on User { 3 | id 4 | name 5 | email 6 | role 7 | } 8 | `; 9 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/__tests__/mutations.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | export const createPost = gql` 4 | mutation createPost($input: CreatePostInput!) { 5 | createPost(input: $input) { 6 | code 7 | message 8 | } 9 | } 10 | `; 11 | 12 | export const createUser = gql` 13 | mutation createUser($input: CreateUserInput!) { 14 | createUser(input: $input) { 15 | code 16 | message 17 | } 18 | } 19 | `; 20 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/__tests__/queries.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | import * as fragments from './fragments'; 3 | 4 | export const user = gql` 5 | query userById($id: ID!) { 6 | user(id: $id) { 7 | ...UserBaseInfo 8 | } 9 | } 10 | ${fragments.userBaseInfo} 11 | `; 12 | 13 | export const userWithBitcoinAddress = gql` 14 | query userByIdWithBitcoinAddress($id: ID!) { 15 | user(id: $id) { 16 | ...UserBaseInfo 17 | bitcoinAddress 18 | } 19 | } 20 | ${fragments.userBaseInfo} 21 | `; 22 | 23 | export const adminUsers = gql` 24 | query adminUsers { 25 | adminUsers { 26 | ...UserBaseInfo 27 | } 28 | } 29 | ${fragments.userBaseInfo} 30 | `; 31 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/db.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | 3 | enum Role { 4 | admin = 'admin', 5 | viewer = 'viewer', 6 | editor = 'editor', 7 | } 8 | 9 | interface IUser { 10 | id: number; 11 | name: string; 12 | email: string; 13 | role: Role; 14 | bitcoinAddress: string; 15 | } 16 | 17 | interface IPost { 18 | id: number; 19 | title: string; 20 | content: string; 21 | authorId: number; 22 | } 23 | 24 | interface IDB { 25 | users: IUser[]; 26 | posts: IPost[]; 27 | } 28 | 29 | const db: IDB = { 30 | users: [ 31 | { 32 | id: 1, 33 | name: faker.name.findName(), 34 | email: faker.internet.email(), 35 | role: Role.admin, 36 | bitcoinAddress: faker.finance.bitcoinAddress(), 37 | }, 38 | { 39 | id: 2, 40 | name: faker.name.findName(), 41 | email: faker.internet.email(), 42 | role: Role.viewer, 43 | bitcoinAddress: faker.finance.bitcoinAddress(), 44 | }, 45 | ], 46 | 47 | posts: [ 48 | { id: 1, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), authorId: 1 }, 49 | { id: 2, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), authorId: 1 }, 50 | { id: 3, title: faker.lorem.sentence(), content: faker.lorem.paragraph(), authorId: 2 }, 51 | ], 52 | }; 53 | 54 | export { db, Role, IUser, IPost, IDB }; 55 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/directive/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/fp/auth.ts: -------------------------------------------------------------------------------- 1 | import { AuthenticationError } from 'apollo-server'; 2 | import { Role } from '../db'; 3 | import { skip } from './'; 4 | 5 | const isAuthenticated = (_, __, { req }) => (req.user ? skip : new AuthenticationError('Not authenticated')); 6 | 7 | const isAuthorized = (roles: Role[]) => (_, __, { req }) => 8 | roles.includes(req.user.role) ? skip : new AuthenticationError('Not authorized'); 9 | 10 | export { isAuthenticated, isAuthorized }; 11 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/fp/combineResolvers.ts: -------------------------------------------------------------------------------- 1 | import { IFieldResolver } from 'graphql-tools'; 2 | 3 | export const skip = undefined; 4 | 5 | export const combineResolvers = (...funcs: Array>): IFieldResolver => (...args) => 6 | funcs.reduce( 7 | (prevPromise, resolver) => prevPromise.then((prev) => (prev === skip ? resolver(...args) : prev)), 8 | Promise.resolve(), 9 | ); 10 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/fp/index.ts: -------------------------------------------------------------------------------- 1 | export * from './combineResolvers'; 2 | export * from './auth'; 3 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | 3 | (async function main() { 4 | await createApolloServer(); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/middleware/auth.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | import { AuthenticationError } from 'apollo-server'; 3 | import { IMiddlewareResolver } from 'graphql-middleware/dist/types'; 4 | 5 | const resolverAuthMap = { 6 | Query: { 7 | user: 'viewer:editor:admin', 8 | adminUsers: 'admin', 9 | config: 'viewer:editor:admin', 10 | }, 11 | Mutation: { 12 | createPost: 'admin', 13 | createUser: 'admin', 14 | }, 15 | User: { 16 | bitcoinAddress: 'admin', 17 | }, 18 | }; 19 | 20 | const authMiddleware: IMiddlewareResolver = async ( 21 | // tslint:disable-next-line: ban-types 22 | resolve: Function, 23 | parent: any, 24 | args: any, 25 | context: any, 26 | info: GraphQLResolveInfo, 27 | ) => { 28 | if (resolverAuthMap[info.parentType.name]) { 29 | const role = resolverAuthMap[info.parentType.name][info.fieldName]; 30 | if (role) { 31 | const roles = role.split(':'); 32 | const { user } = context.req; 33 | console.log( 34 | `[authMiddleware] parentType.name: ${info.parentType.name}, fieldName: ${ 35 | info.fieldName 36 | }, role = ${role}, user = ${JSON.stringify(user)}`, 37 | ); 38 | if (!user || !roles.includes(user.role)) { 39 | throw new AuthenticationError('no permission'); 40 | } 41 | } 42 | } 43 | return resolve(parent, args, context, info); 44 | }; 45 | 46 | export { authMiddleware }; 47 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/ConfigController.ts: -------------------------------------------------------------------------------- 1 | import { auth } from './decorator'; 2 | import { Role } from '../db'; 3 | 4 | class ConfigController { 5 | @auth({ roles: [Role.admin, Role.editor, Role.viewer] }) 6 | public static config() { 7 | return { url: 'https://github.com/mrdulin' }; 8 | } 9 | private constructor() {} 10 | } 11 | 12 | export { ConfigController }; 13 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/PostController.ts: -------------------------------------------------------------------------------- 1 | import { auth } from './decorator'; 2 | import { Role } from '../db'; 3 | 4 | class PostController { 5 | public static posts(_, { ids }, { db }) { 6 | return db.posts.filter((post) => ids.includes(post.id.toString())); 7 | } 8 | @auth({ roles: [Role.admin] }) 9 | public static createPost(_, { input }, { db }) { 10 | const post = { 11 | id: db.posts.length, 12 | ...input, 13 | }; 14 | db.posts.push(post); 15 | return { code: 0, message: 'ok' }; 16 | } 17 | 18 | public static author(post, _, { db }) { 19 | return db.users.find((user) => user.id === post.authorId); 20 | } 21 | private constructor() {} 22 | } 23 | 24 | export { PostController }; 25 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/UserController.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../db'; 2 | import { auth } from './decorator'; 3 | import { defaultFieldResolver } from 'graphql'; 4 | 5 | class UserController { 6 | @auth({ roles: [Role.admin, Role.editor, Role.viewer] }) 7 | public static user(_, { id }, { db }) { 8 | return db.users.find((user) => user.id.toString() === id); 9 | } 10 | @auth({ roles: [Role.admin] }) 11 | public static adminUsers(_, __, { db }) { 12 | return db.users.filter((user) => user.role === Role.admin) || []; 13 | } 14 | @auth({ roles: [Role.admin] }) 15 | public static createUser(_, { input }, { db }) { 16 | const user = { 17 | id: db.users.length, 18 | ...input, 19 | }; 20 | db.users.push(user); 21 | return { code: 0, message: 'ok' }; 22 | } 23 | @auth({ roles: [Role.admin] }) 24 | public static bitcoinAddress(_, __, ___) { 25 | return defaultFieldResolver; 26 | } 27 | 28 | private constructor() {} 29 | } 30 | 31 | export { UserController }; 32 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/decorator/auth.ts: -------------------------------------------------------------------------------- 1 | import { Role } from '../../db'; 2 | import { AuthenticationError } from 'apollo-server'; 3 | 4 | function AuthDecoratorFactory(options?: { roles: Role[] }) { 5 | return function authDecorator(target: any, propertyKey: string, descriptor: TypedPropertyDescriptor) { 6 | const orignalFunction = descriptor.value; 7 | descriptor.value = function(...args: any[]) { 8 | const context = args[2]; 9 | const { user } = context.req; 10 | if (!user) { 11 | throw new AuthenticationError('no permission'); 12 | } 13 | 14 | if (options && options.roles) { 15 | console.log(`[authDecorator] roles = ${options.roles}, user = ${JSON.stringify(user)}`); 16 | if (!options.roles.includes(user.role)) { 17 | throw new AuthenticationError('no permission'); 18 | } 19 | } 20 | return orignalFunction.apply(this, args); 21 | }; 22 | return descriptor; 23 | }; 24 | } 25 | 26 | export { AuthDecoratorFactory as auth }; 27 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/decorator/index.ts: -------------------------------------------------------------------------------- 1 | export * from './auth'; 2 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/oop/index.ts: -------------------------------------------------------------------------------- 1 | export * from './UserController'; 2 | export * from './PostController'; 3 | export * from './ConfigController'; 4 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import { Role } from './db'; 3 | 4 | const resolvers: IResolvers = { 5 | Query: { 6 | user: (_, { id }, { db }) => { 7 | return db.users.find((user) => user.id.toString() === id); 8 | }, 9 | posts: (_, { ids }, { db }) => { 10 | return db.posts.filter((post) => ids.includes(post.id.toString())); 11 | }, 12 | adminUsers: (_, __, { db }) => { 13 | return db.users.filter((user) => user.role === Role.admin) || []; 14 | }, 15 | config: () => { 16 | return { url: 'https://github.com/mrdulin' }; 17 | }, 18 | }, 19 | Mutation: { 20 | createPost: (_, { input }, { db }) => { 21 | const post = { 22 | id: db.posts.length, 23 | ...input, 24 | }; 25 | db.posts.push(post); 26 | return { code: 0, message: 'ok' }; 27 | }, 28 | 29 | createUser: (_, { input }, { db }) => { 30 | const user = { 31 | id: db.users.length, 32 | ...input, 33 | }; 34 | db.users.push(user); 35 | return { code: 0, message: 'ok' }; 36 | }, 37 | }, 38 | 39 | Post: { 40 | author: (post, _, { db }) => { 41 | return db.users.find((user) => user.id === post.authorId); 42 | }, 43 | }, 44 | }; 45 | 46 | export { resolvers }; 47 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/resolversWithClass.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import { UserController, PostController, ConfigController } from './oop'; 3 | 4 | const resolversWithClass: IResolvers = { 5 | Query: { 6 | user: UserController.user, 7 | posts: PostController.posts, 8 | adminUsers: UserController.adminUsers, 9 | config: ConfigController.config, 10 | }, 11 | Mutation: { 12 | createPost: PostController.createPost, 13 | createUser: UserController.createUser, 14 | }, 15 | Post: { 16 | author: PostController.author, 17 | }, 18 | User: { 19 | bitcoinAddress: UserController.bitcoinAddress, 20 | }, 21 | }; 22 | 23 | export { resolversWithClass }; 24 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/schemaWIthMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { applyMiddleware } from 'graphql-middleware'; 2 | import { makeExecutableSchema } from 'graphql-tools'; 3 | 4 | import { authMiddleware } from './middleware'; 5 | import { resolvers } from './resolvers'; 6 | import { typeDefs } from './typeDefs'; 7 | 8 | const schema = makeExecutableSchema({ 9 | typeDefs, 10 | resolvers, 11 | }); 12 | 13 | const schemaWithMiddleware = applyMiddleware(schema, authMiddleware); 14 | export { schemaWithMiddleware as schema }; 15 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/schemaWithClass.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from 'graphql-tools'; 2 | 3 | import { resolversWithClass } from './resolversWithClass'; 4 | import { typeDefs } from './typeDefs'; 5 | 6 | const schema = makeExecutableSchema({ 7 | typeDefs, 8 | resolvers: resolversWithClass, 9 | }); 10 | 11 | export { schema }; 12 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/schemaWithCombineResolvers.ts: -------------------------------------------------------------------------------- 1 | import { resolversWIthCombineResolvers } from './resolversWIthCombineResolvers'; 2 | import { typeDefs } from './typeDefs'; 3 | import { makeExecutableSchema } from 'graphql-tools'; 4 | 5 | const schema = makeExecutableSchema({ 6 | typeDefs, 7 | resolvers: resolversWIthCombineResolvers, 8 | }); 9 | 10 | export { schema }; 11 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/schemaWithDirective.ts: -------------------------------------------------------------------------------- 1 | import { AuthDirective } from './directive'; 2 | import { typeDefs } from './typeDefsWithDirective'; 3 | import { resolvers } from './resolvers'; 4 | import { makeExecutableSchema } from 'graphql-tools'; 5 | 6 | const schema = makeExecutableSchema({ 7 | typeDefs, 8 | resolvers, 9 | schemaDirectives: { 10 | auth: AuthDirective, 11 | authorized: AuthDirective, 12 | authenticated: AuthDirective, 13 | }, 14 | }); 15 | 16 | export { schema }; 17 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer } from 'apollo-server-express'; 3 | import { db } from './db'; 4 | import { schema as schemaWIthMiddleware } from './schemaWIthMiddleware'; 5 | import { schema as schemaWithClass } from './schemaWithClass'; 6 | import { schema as schemaWithCombineResolvers } from './schemaWithCombineResolvers'; 7 | import { schema as schemaWithDirective } from './schemaWithDirective'; 8 | import http from 'http'; 9 | 10 | async function createApolloServer(): Promise { 11 | const PORT = process.env.PORT || 3000; 12 | const app = express(); 13 | 14 | function contextFunction({ req }) { 15 | const token = req.get('authorization'); 16 | // mock jwt auth user 17 | const user = db.users.find((u) => u.id.toString() === token); 18 | req.user = user; 19 | return { db, req }; 20 | } 21 | 22 | const server = new ApolloServer({ 23 | // schema: schemaWIthMiddleware, 24 | // schema: schemaWithClass, 25 | // schema: schemaWithDirective, 26 | schema: schemaWithCombineResolvers, 27 | context: contextFunction, 28 | }); 29 | server.applyMiddleware({ app, path: '/graphql' }); 30 | 31 | return new Promise((resolve) => { 32 | const httpServer = app.listen(PORT, () => { 33 | console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`); 34 | resolve(httpServer); 35 | }); 36 | }); 37 | } 38 | 39 | export { createApolloServer }; 40 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | enum Role { 5 | admin 6 | viewer 7 | editor 8 | } 9 | 10 | type User { 11 | id: ID! 12 | name: String 13 | email: String! 14 | role: Role 15 | bitcoinAddress: String 16 | } 17 | 18 | input CreateUserInput { 19 | name: String 20 | email: String! 21 | role: Role! 22 | } 23 | 24 | type Post { 25 | id: ID! 26 | title: String! 27 | content: String 28 | author: User 29 | } 30 | 31 | type Config { 32 | url: String! 33 | } 34 | 35 | input CreatePostInput { 36 | title: String! 37 | content: String! 38 | } 39 | 40 | type Query { 41 | user(id: ID!): User 42 | posts(ids: [ID!]!): [Post]! 43 | 44 | adminUsers: [User]! 45 | config: Config 46 | } 47 | 48 | type CommonResponse { 49 | code: Int! 50 | message: String! 51 | } 52 | 53 | type Mutation { 54 | createPost(input: CreatePostInput!): CommonResponse! 55 | createUser(input: CreateUserInput!): CommonResponse! 56 | } 57 | `; 58 | 59 | export { typeDefs }; 60 | -------------------------------------------------------------------------------- /src/graphql-authentication-and-authorization/typeDefsWithDirective.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | directive @auth(requires: [Role]) on FIELD_DEFINITION 5 | 6 | enum Role { 7 | admin 8 | viewer 9 | editor 10 | } 11 | 12 | type User { 13 | id: ID! 14 | name: String 15 | email: String! 16 | role: Role 17 | bitcoinAddress: String @auth(requires: [admin]) 18 | } 19 | 20 | input CreateUserInput { 21 | name: String 22 | email: String! 23 | role: Role! 24 | } 25 | 26 | type Post { 27 | id: ID! 28 | title: String! 29 | content: String 30 | author: User 31 | } 32 | 33 | type Config { 34 | url: String! 35 | } 36 | 37 | input CreatePostInput { 38 | title: String! 39 | content: String! 40 | } 41 | 42 | type Query { 43 | user(id: ID!): User @auth(requires: [admin, editor, viewer]) 44 | posts(ids: [ID!]!): [Post]! 45 | 46 | adminUsers: [User]! @auth(requires: [admin]) 47 | config: Config @auth(requires: [admin, editor, viewer]) 48 | } 49 | 50 | type CommonResponse { 51 | code: Int! 52 | message: String! 53 | } 54 | 55 | type Mutation { 56 | createPost(input: CreatePostInput!): CommonResponse! @auth(requires: [admin]) 57 | createUser(input: CreateUserInput!): CommonResponse! @auth(requires: [admin]) 58 | } 59 | `; 60 | 61 | export { typeDefs }; 62 | -------------------------------------------------------------------------------- /src/handle-url-query-parameter/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, ApolloError } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | hello: String 6 | } 7 | `; 8 | const resolvers = { 9 | Query: { 10 | hello: () => 'world', 11 | }, 12 | }; 13 | 14 | const server = new ApolloServer({ 15 | typeDefs, 16 | resolvers, 17 | context: ({ req }) => { 18 | console.log(req.query); 19 | if (!req.query.apikey) { 20 | throw new ApolloError('apikey invalid'); 21 | } 22 | return { 23 | req, 24 | }; 25 | }, 26 | }); 27 | 28 | server.listen().then(({ url }) => { 29 | console.log(`🚀 Server ready at ${url}`); 30 | }); 31 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/index.ts: -------------------------------------------------------------------------------- 1 | export * from './post'; 2 | export * from './tag'; 3 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/post/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchemaModule } from 'apollo-server'; 2 | import { resolvers } from './resolvers'; 3 | import { typeDefs } from './typeDefs'; 4 | 5 | const postModule: GraphQLSchemaModule = { 6 | typeDefs, 7 | resolvers, 8 | }; 9 | export { postModule as Post }; 10 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/post/resolvers.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | const resolvers = { 3 | Query: { 4 | posts: () => { 5 | return [{ id: 1, title: faker.lorem.sentence() }]; 6 | }, 7 | }, 8 | }; 9 | export { resolvers }; 10 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/post/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Post { 5 | id: ID! 6 | title: String! 7 | } 8 | 9 | type Query { 10 | posts: [Post]! 11 | } 12 | `; 13 | 14 | export { typeDefs }; 15 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/tag/index.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLSchemaModule } from 'apollo-server'; 2 | import { resolvers } from './resolvers'; 3 | import { typeDefs } from './typeDefs'; 4 | 5 | const tagModule: GraphQLSchemaModule = { 6 | typeDefs, 7 | resolvers, 8 | }; 9 | export { tagModule as Tag }; 10 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/tag/resolvers.ts: -------------------------------------------------------------------------------- 1 | const resolvers = { 2 | Query: { 3 | tags: () => { 4 | return [{ id: 1, text: 'programming' }]; 5 | }, 6 | }, 7 | }; 8 | 9 | export { resolvers }; 10 | -------------------------------------------------------------------------------- /src/initialize-with-modules/modules/tag/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Tag { 5 | id: ID! 6 | text: String 7 | } 8 | type Query { 9 | tags: [Tag]! 10 | } 11 | `; 12 | 13 | export { typeDefs }; 14 | -------------------------------------------------------------------------------- /src/initialize-with-modules/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { Post, Tag } from './modules'; 3 | 4 | const server = new ApolloServer({ 5 | modules: [Post, Tag], 6 | playground: true, 7 | introspection: true, 8 | }); 9 | 10 | server.listen().then(({ url }) => { 11 | console.log(`apollo server is listening on ${url}`); 12 | }); 13 | -------------------------------------------------------------------------------- /src/introspection-query-issue/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, IResolvers } from 'apollo-server-express'; 2 | import { ContextFunction } from 'apollo-server-core'; 3 | import express from 'express'; 4 | import morgan = require('morgan'); 5 | 6 | const app = express(); 7 | const port = 3000; 8 | const GRAPHQL_PATH = '/graphql'; 9 | 10 | const typeDefs = gql` 11 | type Query { 12 | _: Boolean 13 | } 14 | `; 15 | 16 | const resolvers: IResolvers = { 17 | Query: {}, 18 | }; 19 | 20 | app.use(morgan('combined')); 21 | 22 | const contextFunction: ContextFunction = ({ req }) => { 23 | console.log(req); 24 | return { req }; 25 | }; 26 | 27 | const apolloServer = new ApolloServer({ typeDefs, resolvers, context: contextFunction }); 28 | apolloServer.applyMiddleware({ 29 | app, 30 | path: GRAPHQL_PATH, 31 | }); 32 | 33 | app.listen(port, () => { 34 | console.log(`GraphQL Web Server is listening on http://localhost:${port}${GRAPHQL_PATH}`); 35 | }); 36 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/context.ts: -------------------------------------------------------------------------------- 1 | import { db } from './db'; 2 | 3 | export interface IAppContext { 4 | db: typeof db; 5 | } 6 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/db.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | 3 | export const db = { 4 | users: [ 5 | { 6 | id: 1, 7 | name: faker.name.findName(), 8 | email: faker.internet.email(), 9 | }, 10 | { 11 | id: 2, 12 | name: faker.name.findName(), 13 | email: faker.internet.email(), 14 | }, 15 | ], 16 | posts: [ 17 | { id: 1, title: faker.lorem.word(), authorId: 1 }, 18 | { id: 2, title: faker.lorem.word(), authorId: 1 }, 19 | { id: 3, title: faker.lorem.word(), authorId: 2 }, 20 | ], 21 | }; 22 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/modules/post/schema.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLObjectType, GraphQLID, GraphQLNonNull, GraphQLString, GraphQLSchema } from 'graphql'; 2 | import { IAppContext } from '../../context'; 3 | 4 | const postType = new GraphQLObjectType({ 5 | name: 'Post', 6 | fields: { 7 | id: { type: new GraphQLNonNull(GraphQLID) }, 8 | title: { type: new GraphQLNonNull(GraphQLString) }, 9 | authorId: { type: new GraphQLNonNull(GraphQLID) }, 10 | }, 11 | }); 12 | 13 | const queryType = new GraphQLObjectType({ 14 | name: 'Query', 15 | fields: { 16 | post: { 17 | type: postType, 18 | args: { 19 | id: { type: new GraphQLNonNull(GraphQLID) }, 20 | }, 21 | resolve: (_, { id }, { db }: IAppContext) => { 22 | return db.posts.find((post) => post.id.toString() === id); 23 | }, 24 | }, 25 | }, 26 | }); 27 | 28 | export const postSchema = new GraphQLSchema({ query: queryType }); 29 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/modules/user/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'graphql-tools'; 2 | import { IAppContext } from '../../context'; 3 | 4 | export const resolvers: IResolvers = { 5 | User: { 6 | posts: (user, _, { db }: IAppContext) => { 7 | return db.posts.find((post) => post.authorId === user.id); 8 | }, 9 | }, 10 | Query: { 11 | user: (_, { id }, { db }: IAppContext) => { 12 | return db.users.find((user) => user.id.toString() === id); 13 | }, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/modules/user/schema.ts: -------------------------------------------------------------------------------- 1 | import { resolvers } from './resolvers'; 2 | import { typeDefs } from './typeDefs'; 3 | import { makeExecutableSchema } from 'apollo-server'; 4 | 5 | export const userSchema = makeExecutableSchema({ typeDefs, resolvers }); 6 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/modules/user/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | export const typeDefs = gql` 4 | type User { 5 | id: ID! 6 | name: String 7 | email: String 8 | 9 | # Use constructor type 'postType' 10 | posts: [Post]! 11 | } 12 | 13 | type Query { 14 | user(id: ID!): User 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/schema.ts: -------------------------------------------------------------------------------- 1 | import { mergeSchemas } from 'apollo-server'; 2 | 3 | import { postSchema } from './modules/post/schema'; 4 | import { userSchema } from './modules/user/schema'; 5 | 6 | export const schema = mergeSchemas({ schemas: [postSchema, userSchema] }); 7 | -------------------------------------------------------------------------------- /src/merge-constuctor-types-and-string-types/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, ServerInfo } from 'apollo-server'; 2 | import { schema } from './schema'; 3 | import { db } from './db'; 4 | 5 | const PORT = process.env.PORT || 3000; 6 | const server = new ApolloServer({ schema, context: { db } }); 7 | 8 | server 9 | .listen(PORT) 10 | .then(({ url }: ServerInfo) => { 11 | console.log(`🚀 Server ready at ${url}`); 12 | }) 13 | .catch((error) => { 14 | console.error('Create server failed.'); 15 | console.error(error); 16 | }); 17 | -------------------------------------------------------------------------------- /src/mocking/01-default-mock/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | id: ID! 6 | name: String 7 | age: Int 8 | email: String 9 | addresses: [Address]! 10 | } 11 | 12 | type Address { 13 | city: String 14 | country: String 15 | state: String 16 | } 17 | 18 | type Query { 19 | userById(id: ID!): User 20 | } 21 | `; 22 | 23 | const server = new ApolloServer({ 24 | typeDefs, 25 | mocks: true, 26 | }); 27 | 28 | server.listen().then(({ url }) => { 29 | console.log(`🚀 Server ready at ${url}`); 30 | }); 31 | -------------------------------------------------------------------------------- /src/mocking/02-customizing-mocks/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, MockList } from 'apollo-server'; 2 | import faker from 'faker'; 3 | 4 | const typeDefs = gql` 5 | type User { 6 | id: ID! 7 | name: String 8 | age: Int 9 | email: String 10 | addresses: [Address]! 11 | } 12 | 13 | type Address { 14 | city: String 15 | country: String 16 | state: String 17 | } 18 | 19 | type Query { 20 | userById(id: ID!): User 21 | users: [User] 22 | } 23 | `; 24 | 25 | const mocks = { 26 | User: () => ({ 27 | id: faker.random.uuid(), 28 | name: faker.name.findName(), 29 | age: faker.random.number(100), 30 | email: faker.internet.email(), 31 | }), 32 | Address: () => ({ 33 | city: faker.address.city(), 34 | country: faker.address.country(), 35 | state: faker.address.state(), 36 | }), 37 | Query: () => ({ 38 | users: () => new MockList([2, 6]), 39 | }), 40 | }; 41 | 42 | const server = new ApolloServer({ 43 | typeDefs, 44 | mocks, 45 | }); 46 | 47 | server.listen().then(({ url }) => { 48 | console.log(`🚀 Server ready at ${url}`); 49 | }); 50 | -------------------------------------------------------------------------------- /src/playground-offline/server.ts: -------------------------------------------------------------------------------- 1 | import { gql, IResolvers } from 'apollo-server'; 2 | import express from 'express'; 3 | import path from 'path'; 4 | import { ApolloServer } from 'apollo-server-express'; 5 | 6 | const typeDefs = gql` 7 | type Query { 8 | name: String 9 | } 10 | `; 11 | 12 | const resolvers: IResolvers = { 13 | Query: { 14 | name: () => 'name', 15 | }, 16 | }; 17 | 18 | const PORT = process.env.PORT || 3000; 19 | const app = express(); 20 | const staticRoot = path.resolve(__dirname, '../../node_modules'); 21 | app.use('/node_modules', express.static(staticRoot)); 22 | const server = new ApolloServer({ typeDefs, resolvers, playground: { cdnUrl: './node_modules' } }); 23 | server.applyMiddleware({ app }); 24 | 25 | app.listen(PORT, () => { 26 | console.log(`🚀 Server ready at http://localhost:${PORT}${server.graphqlPath}`); 27 | }); 28 | -------------------------------------------------------------------------------- /src/plugin-and-extensions/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import StackDriverExtension from './stackdiverExtension'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | hello: String 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | hello() { 12 | return 'Hello, World!'; 13 | }, 14 | }, 15 | }; 16 | 17 | const server = new ApolloServer({ typeDefs, resolvers, extensions: [() => new StackDriverExtension()] }); 18 | const port = 3000; 19 | server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`)); 20 | -------------------------------------------------------------------------------- /src/plugin-and-extensions/stackdiverExtension.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServerPlugin, GraphQLResponse } from 'apollo-server-plugin-base'; 2 | 3 | // https://stackoverflow.com/questions/63167965/deprecated-a-stackdriverextension-was-defined-within-the-extensions-configur 4 | class StackDriverExtension implements ApolloServerPlugin { 5 | public requestDidStart() { 6 | console.log('requestDidStart'); 7 | } 8 | 9 | public willSendResponse(response: { graphqlResponse: GraphQLResponse }) { 10 | console.log('willSendResponse'); 11 | } 12 | } 13 | 14 | export default StackDriverExtension; 15 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/db/memoryDB.ts: -------------------------------------------------------------------------------- 1 | import faker from 'faker'; 2 | import { IMemoryDB } from '../types'; 3 | 4 | const userPK1 = '1'; 5 | const userPK2 = '2'; 6 | 7 | const memoryDB: IMemoryDB = { 8 | users: [ 9 | { userId: userPK1, userNme: faker.name.findName(), userEmail: faker.internet.email() }, 10 | { userId: userPK2, userNme: faker.name.findName(), userEmail: faker.internet.email() }, 11 | ], 12 | posts: [ 13 | { 14 | postId: '1', 15 | postTitle: faker.lorem.sentence(), 16 | postCreatedAt: new Date().toUTCString(), 17 | postAuthorId: userPK1, 18 | }, 19 | { 20 | postId: '2', 21 | postTitle: faker.lorem.sentence(), 22 | postCreatedAt: new Date().toUTCString(), 23 | postAuthorId: userPK1, 24 | }, 25 | ], 26 | }; 27 | 28 | export { memoryDB, IMemoryDB }; 29 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/resolvers-better.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'graphql-tools'; 2 | 3 | const resolversBetter: IResolvers = { 4 | Query: { 5 | async postById(_, { id }, { memoryDB }) { 6 | return memoryDB.posts.find((post) => post.postId === id); 7 | }, 8 | async posts(_, __, { memoryDB }) { 9 | return memoryDB.posts; 10 | }, 11 | }, 12 | Post: { 13 | async postAuthor(source, _, { memoryDB }) { 14 | console.count('resolve Post.postAuthor'); 15 | return memoryDB.users.find((user) => user.userId === source.postAuthorId); 16 | }, 17 | }, 18 | }; 19 | 20 | export { resolversBetter }; 21 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | 3 | const resolvers: IResolvers = { 4 | Query: { 5 | async postById(_, { id }, { memoryDB }) { 6 | const postFound = memoryDB.posts.find((post) => post.postId === id); 7 | if (postFound) { 8 | const postAuthor = memoryDB.users.find((user) => user.userId === postFound.postAuthorId); 9 | postFound.postAuthor = postAuthor; 10 | } 11 | return postFound; 12 | }, 13 | async posts(_, __, { memoryDB }) { 14 | return memoryDB.posts.map((post) => { 15 | post.postAuthor = memoryDB.users.find((user) => user.userId === post.postAuthorId); 16 | return post; 17 | }); 18 | }, 19 | }, 20 | }; 21 | 22 | export { resolvers }; 23 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { memoryDB } from './db/memoryDB'; 4 | import { resolvers } from './resolvers'; 5 | import { resolversBetter } from './resolvers-better'; 6 | 7 | const server = new ApolloServer({ typeDefs, resolvers: resolversBetter, context: { memoryDB } }); 8 | 9 | if (process.env.NODE_ENV !== 'test') { 10 | server.listen().then(({ url }) => { 11 | console.log(`🚀 Server ready at ${url}`); 12 | }); 13 | } 14 | 15 | export { server }; 16 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | userId: ID! 6 | userNme: String 7 | userEmail: String 8 | } 9 | 10 | type Post { 11 | postId: ID! 12 | postTitle: String 13 | postCreatedAt: String 14 | postAuthor: User 15 | } 16 | 17 | type Query { 18 | postById(id: ID!): Post 19 | posts: [Post]! 20 | } 21 | `; 22 | 23 | export { typeDefs }; 24 | -------------------------------------------------------------------------------- /src/resolve-fields-in-one-resolver/types.ts: -------------------------------------------------------------------------------- 1 | interface IUser { 2 | userId: string; 3 | userNme: string; 4 | userEmail: string; 5 | } 6 | 7 | interface IPost { 8 | postId: string; 9 | postTitle: string; 10 | postCreatedAt: string; 11 | postAuthorId: string; 12 | } 13 | 14 | interface IMemoryDB { 15 | users: IUser[]; 16 | posts: IPost[]; 17 | } 18 | 19 | export { IUser, IPost, IMemoryDB }; 20 | -------------------------------------------------------------------------------- /src/rest-api-caching/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User 7 | 8 | 9 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /src/rest-api-caching/rest-api-server.test.ts: -------------------------------------------------------------------------------- 1 | import { createServer } from './rest-api-server'; 2 | import request from 'supertest'; 3 | 4 | describe('rest-api-caching', () => { 5 | describe('rest-api-server', () => { 6 | let server; 7 | beforeEach(() => { 8 | server = createServer(); 9 | }); 10 | afterEach((done) => { 11 | server.close(done); 12 | }); 13 | it('should return user', async () => { 14 | const res = await request(server).get('/api/user'); 15 | expect(res.status).toBe(200); 16 | console.log(res.body); 17 | expect(res.body).toHaveProperty('name'); 18 | expect(res.body).toHaveProperty('email'); 19 | }); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/rest-api-caching/rest-api-server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import faker from 'faker'; 3 | 4 | function createServer() { 5 | const app = express(); 6 | const port = 3000; 7 | 8 | app.get('/user', (req, res) => { 9 | res.sendFile('index.html', { root: __dirname }); 10 | }); 11 | 12 | app.get('/api/user', (req, res) => { 13 | console.log(`[${new Date().toLocaleTimeString()}] request user`); 14 | const user = { name: faker.name.findName(), email: faker.internet.email() }; 15 | res.set('Cache-Control', 'public, max-age=30').json(user); 16 | }); 17 | 18 | app.get('/api/project', (req, res) => { 19 | console.log(`[${new Date().toLocaleTimeString()}] request project`); 20 | const project = { name: faker.commerce.productName() }; 21 | res.json(project); 22 | }); 23 | return app.listen(port, () => { 24 | console.log(`HTTP server is listening on http://localhost:${port}`); 25 | }); 26 | } 27 | 28 | if (require.main === module) { 29 | createServer(); 30 | } 31 | 32 | export { createServer }; 33 | -------------------------------------------------------------------------------- /src/rest-link/client.ts: -------------------------------------------------------------------------------- 1 | import { ApolloClient } from 'apollo-client'; 2 | import { RestLink } from 'apollo-link-rest'; 3 | import { InMemoryCache } from 'apollo-cache-inmemory'; 4 | import gql from 'graphql-tag'; 5 | import fetch from 'isomorphic-fetch'; 6 | 7 | const restLink = new RestLink({ 8 | uri: 'http://localhost:3000/api/', 9 | customFetch: fetch, 10 | headers: { 11 | 'Content-Type': 'application/json', 12 | }, 13 | }); 14 | 15 | const client = new ApolloClient({ 16 | cache: new InMemoryCache(), 17 | link: restLink, 18 | }); 19 | 20 | const getbyhashQuery = gql` 21 | fragment Payload on REST { 22 | hash: String 23 | } 24 | query Me($input: Payload!) { 25 | person(input: $input) @rest(type: "Person", method: "POST", path: "transaction/getbyhash") { 26 | email 27 | name 28 | } 29 | } 30 | `; 31 | const payload = { hash: '4e23f9e1d1729996de46fc94d28475b4614f101d72a98f221493f900dc33e0c2' }; 32 | 33 | client.query({ query: getbyhashQuery, variables: { input: payload } }).then((res) => { 34 | console.log(res.data); 35 | }); 36 | -------------------------------------------------------------------------------- /src/rest-link/rest-api-server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import faker from 'faker'; 3 | 4 | const app = express(); 5 | const port = 3000; 6 | 7 | app.use(express.json()); 8 | app.post('/api/transaction/getbyhash', (req, res) => { 9 | console.log(req.body); 10 | res.json({ 11 | email: faker.internet.email(), 12 | name: faker.name.findName(), 13 | }); 14 | }); 15 | 16 | app.listen(port, () => console.log(`HTTP server is listening on http://localhost:${port}`)); 17 | -------------------------------------------------------------------------------- /src/retry-link/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | 3 | let retryCount = 0; 4 | const typeDefs = gql` 5 | type Query { 6 | hello: String 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | hello: () => { 12 | console.log('Retry count: ', ++retryCount); 13 | throw new Error('something bad happened'); 14 | }, 15 | }, 16 | }; 17 | 18 | const server = new ApolloServer({ typeDefs, resolvers }); 19 | 20 | server.listen().then(({ url }) => { 21 | console.log(`Apollo server is listening on ${url}`); 22 | }); 23 | -------------------------------------------------------------------------------- /src/schema-delegation/01-basic/bookService/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, ServerInfo } from 'apollo-server'; 2 | 3 | const MemoryDB = { 4 | books: [ 5 | { id: 1, name: 'GraphQL in Action', userId: 1 }, 6 | { id: 2, name: 'Go', userId: 1 }, 7 | { id: 3, name: 'JavaScript', userId: 1 }, 8 | { id: 4, name: 'Python', userId: 1 }, 9 | { id: 5, name: 'Node.js', userId: 1 }, 10 | ], 11 | }; 12 | 13 | const typeDefs = gql` 14 | type Booking { 15 | id: ID! 16 | name: String 17 | userId: ID 18 | } 19 | 20 | type Query { 21 | bookingsByUser(userId: ID!, limit: Int): [Booking] 22 | } 23 | `; 24 | 25 | const resolvers = { 26 | Query: { 27 | bookingsByUser: async (_, { userId, limit }, { db }) => { 28 | console.log('[user service] Query.bookingsByUser, args:', { userId, limit }); 29 | return db.books.filter((book) => book.userId === Number.parseInt(userId, 10)).slice(0, limit); 30 | }, 31 | }, 32 | }; 33 | 34 | const PORT = 3000; 35 | const server = new ApolloServer({ typeDefs, resolvers, context: { db: MemoryDB } }); 36 | 37 | server 38 | .listen(PORT) 39 | .then(({ url }: ServerInfo) => { 40 | console.log(`🚀 Server ready at ${url}`); 41 | }) 42 | .catch((error) => { 43 | console.error('Create server failed.'); 44 | console.error(error); 45 | }); 46 | -------------------------------------------------------------------------------- /src/schema-delegation/02-delegation-perf-issue/bookService/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, ServerInfo } from 'apollo-server'; 2 | 3 | const MemoryDB = { 4 | books: [ 5 | { id: 1, userId: 1 }, 6 | { id: 2, userId: 1 }, 7 | { id: 3, userId: 1 }, 8 | { id: 4, userId: 1 }, 9 | { id: 5, userId: 1 }, 10 | ], 11 | }; 12 | 13 | const typeDefs = gql` 14 | type Booking { 15 | id: ID! 16 | } 17 | 18 | type Query { 19 | bookingsByUser(userId: ID!, limit: Int): [Booking] 20 | } 21 | `; 22 | 23 | const resolvers = { 24 | Query: { 25 | bookingsByUser: async (_, { userId, limit }, { db }) => { 26 | console.log('[user service] Query.bookingsByUser, args:', { userId, limit }); 27 | return db.books.filter((book) => book.userId === Number.parseInt(userId, 10)).slice(0, limit); 28 | }, 29 | }, 30 | }; 31 | 32 | const PORT = 3000; 33 | const server = new ApolloServer({ typeDefs, resolvers, context: { db: MemoryDB } }); 34 | 35 | server 36 | .listen(PORT) 37 | .then(({ url }: ServerInfo) => { 38 | console.log(`🚀 Server ready at ${url}`); 39 | }) 40 | .catch((error) => { 41 | console.error('Create server failed.'); 42 | console.error(error); 43 | }); 44 | -------------------------------------------------------------------------------- /src/schema-delegation/03-merge-multiple-schemas-perf-issue/gateway.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { getSchema } from './schema'; 3 | import { GraphQLSchema } from 'graphql'; 4 | 5 | async function createApolloServer() { 6 | let schema: GraphQLSchema; 7 | try { 8 | schema = await getSchema(); 9 | } catch (error) { 10 | console.log(error); 11 | process.exit(1); 12 | } 13 | const server = new ApolloServer({ schema }); 14 | const port = 3000; 15 | return server 16 | .listen(port) 17 | .then(({ url }) => console.log(`🚀 Server ready at ${url}`)) 18 | .catch((err) => console.log('start server error', err)); 19 | } 20 | 21 | if (require.main === module) { 22 | createApolloServer(); 23 | } 24 | -------------------------------------------------------------------------------- /src/schema-delegation/03-merge-multiple-schemas-perf-issue/schema.ts: -------------------------------------------------------------------------------- 1 | import { mergeSchemas } from 'graphql-tools'; 2 | import { getUserServiceSchema, getPostServiceSchema, getTagServiceSchema } from './util'; 3 | 4 | export async function getSchemaPerfIssue() { 5 | return mergeSchemas({ 6 | schemas: [await getUserServiceSchema(), await getPostServiceSchema(), await getTagServiceSchema()], 7 | }); 8 | } 9 | 10 | export async function getSchema() { 11 | try { 12 | const schemas = await Promise.all([getUserServiceSchema(), getPostServiceSchema(), getTagServiceSchema()]); 13 | return mergeSchemas({ schemas }); 14 | } catch (error) { 15 | console.log(error); 16 | throw new Error('get schema error'); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/schema-delegation/03-merge-multiple-schemas-perf-issue/util.ts: -------------------------------------------------------------------------------- 1 | import { introspectSchema } from 'apollo-server'; 2 | import { HttpLink } from 'apollo-link-http'; 3 | import { fetch } from 'cross-fetch'; 4 | 5 | async function getRemoteSchema(uri: string) { 6 | const link = new HttpLink({ uri, fetch }); 7 | return introspectSchema(link); 8 | } 9 | export async function getUserServiceSchema() { 10 | return getRemoteSchema('http://localhost:3001/graphql'); 11 | } 12 | export async function getPostServiceSchema() { 13 | return getRemoteSchema('http://localhost:3002/graphql'); 14 | } 15 | export async function getTagServiceSchema() { 16 | return getRemoteSchema('http://localhost:3003/graphql'); 17 | } 18 | -------------------------------------------------------------------------------- /src/schema-stitching-issue/service-a.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | a: String 6 | } 7 | type Query { 8 | user: User 9 | } 10 | `; 11 | const resolvers = { 12 | Query: { 13 | user: () => ({ a: 'a' }), 14 | }, 15 | }; 16 | 17 | const server = new ApolloServer({ typeDefs, resolvers }); 18 | const port = 3001; 19 | server.listen(port).then(({ url }) => console.log(`Service A is ready at ${url}`)); 20 | -------------------------------------------------------------------------------- /src/schema-stitching-issue/service-b.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type User { 5 | b: String 6 | } 7 | type Query { 8 | user: User 9 | } 10 | `; 11 | const resolvers = { 12 | Query: { 13 | user: () => ({ b: 'b' }), 14 | }, 15 | }; 16 | 17 | const server = new ApolloServer({ typeDefs, resolvers }); 18 | const port = 3002; 19 | server.listen(port).then(({ url }) => console.log(`Service B is ready at ${url}`)); 20 | -------------------------------------------------------------------------------- /src/serialization-and-deserialization/gateway.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import axios from 'axios'; 3 | 4 | const app = express(); 5 | const port = 3001; 6 | 7 | app.get('/users', (req, res) => { 8 | axios 9 | .post( 10 | 'http://localhost:3000/graphql', 11 | { 12 | query: ` 13 | query { 14 | users { 15 | id 16 | email 17 | firstName 18 | lastName 19 | } 20 | } 21 | `, 22 | }, 23 | { headers: { 'content-type': 'application/json' } }, 24 | ) 25 | .then((response) => res.json(response.data)) 26 | .catch(console.log); 27 | }); 28 | 29 | app.listen(port, () => console.log(`gateway is listening on http://localhost:${port}`)); 30 | -------------------------------------------------------------------------------- /src/serialization-and-deserialization/userService.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import faker from 'faker'; 3 | 4 | const typeDefs = gql` 5 | type User { 6 | id: ID! 7 | email: String! 8 | firstName: String 9 | lastName: String 10 | } 11 | 12 | type Query { 13 | users: [User]! 14 | } 15 | `; 16 | 17 | const users: any[] = []; 18 | for (let i = 0; i < 1000; i++) { 19 | users.push({ 20 | id: faker.random.uuid(), 21 | email: faker.internet.email(), 22 | firstName: faker.name.firstName(), 23 | lastName: faker.name.lastName(), 24 | }); 25 | } 26 | 27 | const MemoryDb = { 28 | users, 29 | }; 30 | 31 | const resolvers = { 32 | Query: { 33 | users(_, __, { db }) { 34 | return db.users; 35 | }, 36 | }, 37 | }; 38 | 39 | const server = new ApolloServer({ 40 | typeDefs, 41 | resolvers, 42 | context: { 43 | db: MemoryDb, 44 | }, 45 | }); 46 | const port = 3000; 47 | 48 | server.listen(port).then(({ url }) => console.log(`user service is listening on ${url}`)); 49 | -------------------------------------------------------------------------------- /src/set-response-header/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { GraphQLResponse, GraphQLRequestContext } from 'apollo-server-express/node_modules/apollo-server-core'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | _root: String 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | _root: () => '_root', 12 | }, 13 | }; 14 | 15 | const server = new ApolloServer({ 16 | typeDefs, 17 | resolvers, 18 | formatResponse: (response: GraphQLResponse | null, requestContext: GraphQLRequestContext) => { 19 | if (requestContext.response && requestContext.response.http) { 20 | requestContext.response.http.headers.set('custom-key', 'custom-value'); 21 | } 22 | return response as GraphQLResponse; 23 | }, 24 | }); 25 | 26 | server.listen().then(({ url }) => { 27 | console.log(`Apollo server is listening on ${url}`); 28 | }); 29 | -------------------------------------------------------------------------------- /src/stackoverflow/40387508/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { ApolloServerPlugin } from 'apollo-server-plugin-base'; 3 | 4 | function customHTTPStatusPlugin(): ApolloServerPlugin { 5 | return { 6 | requestDidStart(requestContext) { 7 | return { 8 | willSendResponse({ errors, response }) { 9 | if (response && response.http) { 10 | if (errors) { 11 | response.data = undefined; 12 | response.http.status = 500; 13 | } 14 | } 15 | }, 16 | }; 17 | }, 18 | }; 19 | } 20 | 21 | const typeDefs = gql` 22 | type Query { 23 | hello: String 24 | } 25 | `; 26 | const resolvers = { 27 | Query: { 28 | hello() { 29 | throw new Error('something happened'); 30 | }, 31 | }, 32 | }; 33 | 34 | const server = new ApolloServer({ typeDefs, resolvers, plugins: [customHTTPStatusPlugin()] }); 35 | const port = 3000; 36 | server.listen(port).then(({ url }) => console.log(`Server is ready at ${url}`)); 37 | -------------------------------------------------------------------------------- /src/stackoverflow/57243105/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server-express'; 2 | import express from 'express'; 3 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | personDetails: String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | personDetails: async (root, args, ctx, info) => { 13 | console.log(`[${new Date().toLocaleTimeString()}] Query.personDetails`); 14 | info.cacheControl.setCacheHint({ maxAge: 10 }); 15 | return 'This is person details'; 16 | }, 17 | }, 18 | }; 19 | 20 | const app = express(); 21 | const apolloServer = new ApolloServer({ 22 | typeDefs, 23 | resolvers, 24 | plugins: [responseCachePlugin()], 25 | }); 26 | 27 | apolloServer.applyMiddleware({ app }); 28 | 29 | const server = app.listen({ port: 4000 }, () => { 30 | console.log(`The server is running in http://localhost:4000${apolloServer.graphqlPath}`); 31 | }); 32 | -------------------------------------------------------------------------------- /src/stackoverflow/57802229/server.test.ts: -------------------------------------------------------------------------------- 1 | import { server } from './server'; 2 | import { createTestClient } from 'apollo-server-testing'; 3 | import { gql } from 'apollo-server'; 4 | 5 | const viewerMutation = gql` 6 | mutation { 7 | viewerMutation { 8 | createElement(input: { name: null }) { 9 | id 10 | } 11 | } 12 | } 13 | `; 14 | 15 | describe('57802229', () => { 16 | it('should pass', async () => { 17 | const { mutate } = createTestClient(server); 18 | const res = await mutate({ mutation: viewerMutation }); 19 | console.log(res); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /src/stackoverflow/57809293/index.ts: -------------------------------------------------------------------------------- 1 | class UserAPI { 2 | public async getPlanById(planId) { 3 | const promise1 = Promise.resolve({ user_ids: [1, 2] }); 4 | const promise2 = Promise.resolve(); 5 | 6 | const objToReturn = await Promise.all([promise1, promise2]) 7 | .then(([snapshot1, snapshot2]) => { 8 | return snapshot1.user_ids; 9 | }) 10 | .then((userIds) => { 11 | return this.getUsersFromUserIds(userIds).then((usersArray) => { 12 | return usersArray; 13 | }); 14 | }); 15 | return objToReturn; 16 | } 17 | 18 | public async getUsersFromUserIds(userIds) { 19 | return [ 20 | { id: 1, name: 'a' }, 21 | { id: 2, name: 'b' }, 22 | ]; 23 | } 24 | } 25 | 26 | const userAPI = new UserAPI(); 27 | userAPI.getPlanById(1).then((objToReturn) => { 28 | console.log('objToReturn: ', objToReturn); 29 | }); 30 | -------------------------------------------------------------------------------- /src/stackoverflow/57924628/index.js: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import express from 'express'; 3 | 4 | global.myTest = true; 5 | 6 | require('./models/user'); 7 | -------------------------------------------------------------------------------- /src/stackoverflow/57924628/models/user.js: -------------------------------------------------------------------------------- 1 | console.log('test:' + global.myTest); 2 | -------------------------------------------------------------------------------- /src/stackoverflow/58226940/server.spec.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server-express'; 2 | import { server, contextFunction } from './server'; 3 | 4 | describe('server', () => { 5 | it('should initialize apollo server', () => { 6 | expect(server).toBeInstanceOf(ApolloServer); 7 | }); 8 | it('should create context', () => { 9 | const mockedReq = {}; 10 | const mockedRes = {}; 11 | const actualValue = contextFunction({ req: mockedReq, res: mockedRes }); 12 | expect(actualValue).toEqual({ req: mockedReq, res: mockedRes }); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/stackoverflow/58226940/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server-express'; 2 | 3 | const typeDefs = gql` 4 | type Query { 5 | _: Boolean 6 | } 7 | `; 8 | 9 | function contextFunction({ req, res }) { 10 | return { req, res }; 11 | } 12 | 13 | const server = new ApolloServer({ 14 | typeDefs, 15 | context: contextFunction, 16 | }); 17 | 18 | export { server, contextFunction }; 19 | -------------------------------------------------------------------------------- /src/stackoverflow/58586576/server.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from 'apollo-server'; 2 | import { ApolloServer, gql } from 'apollo-server-hapi'; 3 | import Hapi from 'hapi'; 4 | import { graphqlHapi } from './hapiApollo'; 5 | 6 | const typeDefs = gql` 7 | type Query { 8 | _: String 9 | } 10 | `; 11 | const resolvers = { 12 | Query: { 13 | _: () => { 14 | throw new Error('some error'); 15 | }, 16 | }, 17 | }; 18 | const schema = makeExecutableSchema({ typeDefs, resolvers }); 19 | const port = 3000; 20 | async function StartServer() { 21 | const app = new Hapi.Server({ port }); 22 | graphqlHapi.register(app, { path: '/graphql', graphqlOptions: { schema } }); 23 | app.ext('onPreResponse', (request: any, h: any) => { 24 | const response = request.response; 25 | if (!response.isBoom) { 26 | return h.continue; 27 | } 28 | return h.response({ message: response.message }).code(400); 29 | }); 30 | 31 | await app.start(); 32 | } 33 | 34 | StartServer() 35 | .then(() => { 36 | console.log(`apollo server is listening on http://localhost:${port}/graphql`); 37 | }) 38 | .catch((error) => console.log(error)); 39 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/constants.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | users: [{ id: 1 }], 3 | tasks: [{ id: 1, userId: 1 }], 4 | }; 5 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/resolvers/index.js: -------------------------------------------------------------------------------- 1 | const userResolver = require('./user'); 2 | const taskResolver = require('./task'); 3 | 4 | module.exports = { 5 | userResolver, 6 | taskResolver, 7 | }; 8 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/resolvers/task.js: -------------------------------------------------------------------------------- 1 | const uuid = require('uuid'); 2 | const { tasks, users } = require('../constants'); 3 | 4 | module.exports = { 5 | Query: { 6 | tasks: () => { 7 | return tasks; 8 | }, 9 | task: (_, { id }) => { 10 | return tasks.find((task) => task.id === id); 11 | }, 12 | }, 13 | Mutation: { 14 | createTask: (_, { input }) => { 15 | const task = { ...input, id: uuid.v4() }; 16 | tasks.push(task); 17 | return task; 18 | }, 19 | }, 20 | Task: { 21 | user: ({ userId }) => users.find((user) => user.id === userId), 22 | }, 23 | }; 24 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/resolvers/user.js: -------------------------------------------------------------------------------- 1 | const { users, tasks } = require('../constants'); 2 | 3 | module.exports = { 4 | Query: { 5 | users: () => users, 6 | user: (_, { id }) => users.find((user) => user.id === id), 7 | }, 8 | Mutation: { 9 | signup: async (_, { input }) => { 10 | return ''; 11 | }, 12 | }, 13 | User: { 14 | tasks: ({ id }) => tasks.filter((task) => task.userId === id), 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { ApolloServer } = require('apollo-server-express'); 3 | const cors = require('cors'); 4 | const dotEnv = require('dotenv'); 5 | const { merge } = require('lodash'); 6 | 7 | const resolvers = require('./resolvers'); 8 | const typeDefs = require('./typeDefs'); 9 | 10 | dotEnv.config(); 11 | 12 | const app = express(); 13 | app.use(cors()); 14 | app.use(express.json()); 15 | 16 | const apolloServer = new ApolloServer({ 17 | typeDefs, 18 | resolvers: merge(resolvers.taskResolver, resolvers.userResolver), 19 | }); 20 | 21 | apolloServer.applyMiddleware({ app, path: '/graphql' }); 22 | 23 | const PORT = process.env.PORT || 3000; 24 | 25 | app.use('/', (req, res) => res.send('Hello world...')); 26 | 27 | app.listen(PORT, () => console.log(`Server listen on port: ${PORT}`)); 28 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/typeDefs/index.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('apollo-server-express'); 2 | 3 | const userTypeDefs = require('./user'); 4 | const taskTypeDefs = require('./task'); 5 | 6 | const typeDefs = gql` 7 | type Query { 8 | _: String 9 | } 10 | type Mutation { 11 | _: String 12 | } 13 | `; 14 | 15 | module.exports = [typeDefs, userTypeDefs, taskTypeDefs]; 16 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/typeDefs/task.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('apollo-server-express'); 2 | 3 | module.exports = gql` 4 | extend type Query { 5 | tasks: [Task!] 6 | task(id: ID!): Task 7 | } 8 | input createTaskInput { 9 | name: String! 10 | completed: Boolean! 11 | userId: ID! 12 | } 13 | extend type Mutation { 14 | createTask(input: createTaskInput!): Task 15 | } 16 | type Task { 17 | id: ID! 18 | name: String! 19 | completed: Boolean! 20 | user: User! 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/stackoverflow/58611959/typeDefs/user.js: -------------------------------------------------------------------------------- 1 | const { gql } = require('apollo-server-express'); 2 | 3 | module.exports = gql` 4 | extend type Query { 5 | users: [User!] 6 | user(id: ID!): User 7 | } 8 | extend type Mutation { 9 | signup(input: signupInput): User 10 | } 11 | input signupInput { 12 | name: String! 13 | email: String! 14 | password: String! 15 | } 16 | type User { 17 | id: ID! 18 | name: String! 19 | email: String! 20 | tasks: [Task!] 21 | } 22 | `; 23 | -------------------------------------------------------------------------------- /src/stackoverflow/58743670-todo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | User 7 | 8 | 9 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/stackoverflow/58743670-todo/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 4 | import faker from 'faker'; 5 | 6 | const typeDefs = gql` 7 | type User @cacheControl(maxAge: 30) { 8 | name: String 9 | email: String 10 | } 11 | type Query { 12 | user: User 13 | } 14 | `; 15 | 16 | const resolvers = { 17 | Query: { 18 | user: async (_, __, context, info) => { 19 | console.count('resolve user'); 20 | const user = { name: faker.name.findName(), email: faker.internet.exampleEmail() }; 21 | return user; 22 | }, 23 | }, 24 | }; 25 | 26 | const app = express(); 27 | const port = 3001; 28 | const graphqlPath = '/graphql'; 29 | const server = new ApolloServer({ 30 | typeDefs, 31 | resolvers, 32 | tracing: true, 33 | // use LRU-memory cache by default 34 | plugins: [responseCachePlugin()], 35 | cacheControl: { 36 | defaultMaxAge: 0, 37 | }, 38 | }); 39 | 40 | server.applyMiddleware({ app, path: graphqlPath }); 41 | 42 | app.get('/', (req, res) => { 43 | res.sendFile('index.html', { root: __dirname }); 44 | }); 45 | 46 | if (require.main === module) { 47 | app.listen(port, () => { 48 | console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`); 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/stackoverflow/59631413/someDatasource.ts: -------------------------------------------------------------------------------- 1 | import { RESTDataSource } from 'apollo-datasource-rest'; 2 | import { Response, Request } from 'apollo-server-env'; 3 | 4 | class SomeDataSource extends RESTDataSource { 5 | protected async didReceiveResponse(res: Response, req: Request): Promise { 6 | if (res.status === 404) { 7 | return (null as any) as Promise; 8 | } 9 | 10 | return super.didReceiveResponse(res, req); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/stackoverflow/59654437/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { GraphQLError } from 'graphql'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | _: Boolean 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | _: () => { 12 | throw new Error('error'); 13 | }, 14 | }, 15 | }; 16 | const server = new ApolloServer({ 17 | typeDefs, 18 | resolvers, 19 | formatError(error: GraphQLError) { 20 | return error.message as any; 21 | }, 22 | }); 23 | 24 | server.listen().then(({ url }) => { 25 | console.log(`Apollo server is listening on ${url}`); 26 | }); 27 | -------------------------------------------------------------------------------- /src/stackoverflow/59677639/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import graphql from 'graphql.js'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | _: String 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | _: () => 'Hello', 12 | }, 13 | }; 14 | const server = new ApolloServer({ 15 | typeDefs, 16 | resolvers, 17 | }); 18 | server.listen().then(async ({ url }) => { 19 | console.log(`Apollo server is listening on ${url}graphql`); 20 | const graph = graphql(`${url}graphql`, { asJSON: true }); 21 | const helloQuery = graph(` 22 | query { 23 | _ 24 | } 25 | `); 26 | const actual = await helloQuery(); 27 | console.log('actual: ', actual); 28 | server.stop(); 29 | }); 30 | -------------------------------------------------------------------------------- /src/stackoverflow/59829676/getAssetIdFromService.test.ts: -------------------------------------------------------------------------------- 1 | import { getAssetIdFromService, GET_ASSET_ID } from './getAssetIdFromService'; 2 | import { setupApi } from './setup'; 3 | 4 | jest.mock('./setup.ts', () => { 5 | const mApolloClient = { query: jest.fn() }; 6 | return { setupApi: jest.fn(() => mApolloClient) }; 7 | }); 8 | 9 | describe('59829676', () => { 10 | afterEach(() => { 11 | jest.clearAllMocks(); 12 | }); 13 | it('should query and return data', async () => { 14 | const client = setupApi(); 15 | const mGraphQLResponse = { data: {}, loading: false, errors: [] }; 16 | client.query.mockResolvedValueOnce(mGraphQLResponse); 17 | const { data, loading, errors } = await getAssetIdFromService('e1'); 18 | expect(client.query).toBeCalledWith({ query: GET_ASSET_ID, variables: { externalId: 'e1' } }); 19 | expect(data).toEqual({}); 20 | expect(loading).toBeFalsy(); 21 | expect(errors).toEqual([]); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/stackoverflow/59829676/getAssetIdFromService.ts: -------------------------------------------------------------------------------- 1 | import { setupApi } from './setup'; 2 | import { gql } from 'apollo-server'; 3 | 4 | const client = setupApi(); 5 | 6 | export const GET_ASSET_ID = gql` 7 | query getAssetByExternalId($externalId: String!) { 8 | assetId: getAssetId(externalId: $externalId) { 9 | id 10 | } 11 | } 12 | `; 13 | 14 | export const getAssetIdFromService = async (externalId: string) => { 15 | return await client.query({ 16 | query: GET_ASSET_ID, 17 | variables: { externalId }, 18 | }); 19 | }; 20 | -------------------------------------------------------------------------------- /src/stackoverflow/59829676/setup.ts: -------------------------------------------------------------------------------- 1 | export const setupApi = (): any => { 2 | // whatever 3 | }; 4 | -------------------------------------------------------------------------------- /src/stackoverflow/60321422/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | 3 | const app = express(); 4 | const port = process.env.PORT || 3000; 5 | 6 | app.get('/api/genres', (req, res) => { 7 | console.log('api genres'); 8 | res.sendStatus(200); 9 | }); 10 | 11 | const server = app.listen(port, () => console.log(`Listening on port ${port}`)); 12 | 13 | module.exports = server; 14 | -------------------------------------------------------------------------------- /src/stackoverflow/60321422/index.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | 3 | let server; 4 | 5 | describe('/api/genres', () => { 6 | beforeEach(() => { 7 | server = require('./index'); 8 | }); 9 | afterEach(() => { 10 | server.close(); 11 | }); 12 | 13 | describe('GET /', () => { 14 | it('should return all genres', async () => { 15 | const res = await request(server).get('/api/genres'); 16 | expect(res.status).toBe(200); 17 | }); 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /src/stackoverflow/60344588/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server-express'; 2 | import express from 'express'; 3 | import { sleep } from '../../util'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | hello: String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | hello: async () => { 13 | console.log('query hello'); 14 | await sleep(1000); 15 | return 'world'; 16 | }, 17 | }, 18 | }; 19 | 20 | const app = express(); 21 | const apolloServer = new ApolloServer({ 22 | typeDefs, 23 | resolvers, 24 | playground: true, 25 | introspection: true, 26 | }); 27 | 28 | apolloServer.applyMiddleware({ app }); 29 | 30 | const server = app.listen({ port: 4000 }, () => { 31 | console.log(`The server is running in http://localhost:4000${apolloServer.graphqlPath}`); 32 | }); 33 | 34 | server.setTimeout(10); 35 | -------------------------------------------------------------------------------- /src/stackoverflow/60977550/MyCache.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueCache, KeyValueCacheSetOptions } from 'apollo-server-caching'; 2 | 3 | export default class MyCache implements KeyValueCache { 4 | private cachedInfo = {}; 5 | 6 | public async set(key: string, value: string, options?: KeyValueCacheSetOptions): Promise { 7 | this.cachedInfo[key] = value; 8 | } 9 | 10 | public async get(key: string): Promise { 11 | if (this.cachedInfo[key]) { 12 | return this.cachedInfo[key]; 13 | } 14 | } 15 | 16 | public async delete(key: string): Promise { 17 | this.cachedInfo[key] = null; 18 | return this.cachedInfo[key] === null; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/stackoverflow/60977550/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | import faker from 'faker'; 4 | import responseCachePlugin from 'apollo-server-plugin-response-cache'; 5 | import MyCache from './MyCache'; 6 | 7 | const typeDefs = gql` 8 | type User @cacheControl(maxAge: 30) { 9 | name: String 10 | email: String 11 | } 12 | type Query { 13 | user: User 14 | } 15 | `; 16 | 17 | const resolvers = { 18 | Query: { 19 | user: async () => { 20 | console.count('resolve user'); 21 | const user = { name: faker.name.findName(), email: faker.internet.exampleEmail() }; 22 | return user; 23 | }, 24 | }, 25 | }; 26 | 27 | const app = express(); 28 | const port = 3001; 29 | const graphqlPath = '/graphql'; 30 | const server = new ApolloServer({ 31 | typeDefs, 32 | resolvers, 33 | plugins: [responseCachePlugin()], 34 | cache: new MyCache(), 35 | }); 36 | 37 | server.applyMiddleware({ app, path: graphqlPath }); 38 | 39 | if (require.main === module) { 40 | app.listen(port, () => { 41 | console.log(`Apollo server is listening on http://localhost:${port}${graphqlPath}`); 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/stackoverflow/61183199/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 61183199 7 | 8 | 9 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/stackoverflow/61183199/index.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server-express'; 2 | import express from 'express'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | dummy: String! 7 | } 8 | `; 9 | const resolvers = { 10 | Query: { 11 | dummy: () => 'hello world', 12 | }, 13 | }; 14 | 15 | const server = new ApolloServer({ 16 | typeDefs, 17 | resolvers, 18 | context: ({ req, res }) => { 19 | return { 20 | response: { 21 | headers: { 22 | 'set-cookie': ['key1=value1', 'key2=value2'], 23 | }, 24 | }, 25 | res, 26 | }; 27 | }, 28 | formatResponse: (response, requestContext: any) => { 29 | // not working 30 | // requestContext.response!.http!.headers.set('set-cookie', 'key1=value1'); 31 | // requestContext.response!.http!.headers.set('set-cookie', 'key2=value2'); 32 | // works fine 33 | requestContext.context.res.set('set-cookie', ['key1=value1', 'key2=value2']); 34 | return response!; 35 | }, 36 | }); 37 | const app = express(); 38 | server.applyMiddleware({ app }); 39 | 40 | app.get('/', (req, res) => { 41 | res.sendFile('index.html', { root: __dirname }); 42 | }); 43 | 44 | app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)); 45 | -------------------------------------------------------------------------------- /src/stackoverflow/61339683/server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer } from 'apollo-server-express'; 3 | import { gql } from 'apollo-server-express'; 4 | 5 | const users = [ 6 | { id: 1, email: 'a@a.a', password: 'zaq1@WSX', pons: [{ value: 'test' }] }, 7 | { id: 2, email: 'b@b.b', password: 'ZAQ!2wsx', pons: [{ value: 'tset' }] }, 8 | ]; 9 | const pons = [{ value: 'test' }]; 10 | 11 | const typeDefs = gql` 12 | type Pon { 13 | value: String! 14 | } 15 | type User { 16 | id: Int 17 | email: String! 18 | password: String! 19 | pons: [Pon]! 20 | } 21 | type Query { 22 | findUser(id: Int): User 23 | users: [User] 24 | pons: [Pon] 25 | } 26 | `; 27 | 28 | const resolvers = { 29 | Query: { 30 | users: () => users, 31 | pons: () => pons, 32 | findUser: (_, { id }) => users.find((u) => u.id === id), 33 | }, 34 | }; 35 | 36 | const server = new ApolloServer({ typeDefs, resolvers }); 37 | 38 | const app = express(); 39 | server.applyMiddleware({ app }); 40 | 41 | app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)); 42 | -------------------------------------------------------------------------------- /src/stackoverflow/61425326/MoviesAPI.ts: -------------------------------------------------------------------------------- 1 | import { RESTDataSource } from 'apollo-datasource-rest'; 2 | 3 | export class MoviesAPI extends RESTDataSource { 4 | public async getMovies() { 5 | const songs = await this.context.dataSources.songsAPI.getSongs(); 6 | const movies = ['a', 'b']; 7 | return JSON.stringify({ movies, songs }); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/stackoverflow/61425326/SongsAPI.ts: -------------------------------------------------------------------------------- 1 | import { RESTDataSource } from 'apollo-datasource-rest'; 2 | 3 | export class SongsAPI extends RESTDataSource { 4 | public async getSongs() { 5 | return ['x', 'y']; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/stackoverflow/61425326/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import { MoviesAPI } from './MoviesAPI'; 3 | import { SongsAPI } from './SongsAPI'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | movies: String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | movies: (_, __, { dataSources }) => { 13 | return dataSources.moviesAPI.getMovies(); 14 | }, 15 | }, 16 | }; 17 | 18 | const server = new ApolloServer({ 19 | typeDefs, 20 | resolvers, 21 | dataSources: () => { 22 | return { 23 | moviesAPI: new MoviesAPI(), 24 | songsAPI: new SongsAPI(), 25 | }; 26 | }, 27 | }); 28 | 29 | server.listen().then(({ url }) => { 30 | console.log(`Apollo server is listening on ${url}`); 31 | }); 32 | -------------------------------------------------------------------------------- /src/stackoverflow/61601783/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql, makeExecutableSchema } from 'apollo-server-express'; 2 | import express from 'express'; 3 | import http from 'http'; 4 | 5 | const corsConfig = { 6 | credentials: true, 7 | allowedHeaders: ['Authorization'], 8 | exposedHeaders: ['Authorization'], 9 | }; 10 | 11 | const typeDefs = gql` 12 | type Query { 13 | hello: String 14 | } 15 | `; 16 | const resolvers = { 17 | Query: { 18 | hello: (_, __, { req }) => { 19 | console.log(req.headers); 20 | return 'world'; 21 | }, 22 | }, 23 | }; 24 | const schema = makeExecutableSchema({ typeDefs, resolvers }); 25 | 26 | const app = express(); 27 | const path = '/graphql'; 28 | const port = 3000; 29 | const server = new ApolloServer({ 30 | schema, 31 | context: ({ req }) => { 32 | return { 33 | req, 34 | }; 35 | }, 36 | }); 37 | server.applyMiddleware({ app, path, cors: corsConfig }); 38 | 39 | http.createServer(app).listen(port, () => console.info(`Service started on port ${port}`)); 40 | 41 | // test 42 | // curl -X POST -H "Content-Type: application/json" -H "Authorization: Bearer abc123" --data '{ "query": "{ hello }" }' http://localhost:3000/graphql 43 | -------------------------------------------------------------------------------- /src/stackoverflow/62122142/schema.ts: -------------------------------------------------------------------------------- 1 | import { gql, makeExecutableSchema } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type EpUserData { 5 | id: ID! 6 | user_presence: String 7 | user_presence_time_of_last_update: String 8 | } 9 | type Query { 10 | dummy: String 11 | } 12 | type Mutation { 13 | cronJobToFindUsersWhoHaveGoneOffline(timeStarted: String): EpUserData 14 | } 15 | `; 16 | 17 | const resolvers = { 18 | Mutation: { 19 | async cronJobToFindUsersWhoHaveGoneOffline(parent, args, context) { 20 | const usersWhoWentOffline = { id: 1, user_presence: 'test', user_presence_time_of_last_update: '2020' }; 21 | return Promise.resolve() 22 | .then(() => { 23 | return usersWhoWentOffline; 24 | }) 25 | .catch((err) => { 26 | console.log(err); 27 | }); 28 | }, 29 | }, 30 | }; 31 | 32 | const schema = makeExecutableSchema({ typeDefs, resolvers }); 33 | 34 | export { schema }; 35 | -------------------------------------------------------------------------------- /src/stackoverflow/62122142/server.test.ts: -------------------------------------------------------------------------------- 1 | import { graphql } from 'graphql'; 2 | import { schema } from './schema'; 3 | import { server } from './server'; 4 | 5 | const CRON_JOB_TO_FIND_USERS_WHO_HAVE_GONE_OFFLINE_MUTATION = ` 6 | mutation ($timeStarted: String){ 7 | cronJobToFindUsersWhoHaveGoneOffline(timeStarted: $timeStarted){ 8 | id, 9 | user_presence, 10 | user_presence_time_of_last_update 11 | }, 12 | } 13 | `; 14 | 15 | describe('62122142', () => { 16 | beforeAll(async () => { 17 | const { url } = await server.listen(); 18 | console.log(`server is listening on ${url}`); 19 | }); 20 | afterAll(async () => { 21 | await server.stop(); 22 | }); 23 | it('should pass', async () => { 24 | const { data, errors } = await graphql( 25 | schema, 26 | CRON_JOB_TO_FIND_USERS_WHO_HAVE_GONE_OFFLINE_MUTATION, 27 | {}, 28 | { caller: 'synced-cron' }, 29 | { 30 | timeStarted: new Date() 31 | .toISOString() 32 | .slice(0, 19) 33 | .replace('T', ' '), 34 | }, 35 | ); 36 | console.log('data', data); 37 | console.log('errors', errors); 38 | 39 | return true; 40 | }); 41 | }); 42 | -------------------------------------------------------------------------------- /src/stackoverflow/62122142/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { schema } from './schema'; 3 | 4 | const server = new ApolloServer({ schema }); 5 | 6 | export { server }; 7 | -------------------------------------------------------------------------------- /src/stackoverflow/62916075/greet.graphql: -------------------------------------------------------------------------------- 1 | type Query { 2 | greeting: String 3 | } 4 | -------------------------------------------------------------------------------- /src/stackoverflow/62916075/resolver.js: -------------------------------------------------------------------------------- 1 | const Query = { 2 | greeting: () => 'Hello World From NightDevs', 3 | }; 4 | 5 | module.exports = { Query }; 6 | -------------------------------------------------------------------------------- /src/stackoverflow/62916075/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { ApolloServer } = require('apollo-server-express'); 3 | const { importSchema } = require('graphql-import'); 4 | const path = require('path'); 5 | 6 | const apolloServer = new ApolloServer({ 7 | typeDefs: importSchema(path.resolve(__dirname, './greet.graphql')), 8 | resolvers: require('./resolver'), 9 | }); 10 | 11 | const app = express(); 12 | const graphqlEndpoint = '/graphql'; 13 | apolloServer.applyMiddleware({ app, path: graphqlEndpoint }); 14 | 15 | app.listen(8080, () => { 16 | console.log('Server Hosted'); 17 | }); 18 | -------------------------------------------------------------------------------- /src/stackoverflow/63181608/datasource.ts: -------------------------------------------------------------------------------- 1 | import { RESTDataSource } from 'apollo-datasource-rest'; 2 | import FormData from 'form-data'; 3 | 4 | export default class MyDatasource extends RESTDataSource { 5 | public async postFileToServer({ str }) { 6 | const inMemoryFile = Buffer.from(str, 'utf-8'); 7 | const myForm = new FormData(); 8 | myForm.append('file', inMemoryFile, 'file.txt'); 9 | const url = 'http://localhost:3000/upload'; 10 | 11 | return this.post(url, myForm); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/stackoverflow/63181608/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, gql } from 'apollo-server'; 2 | import MyDatasource from './datasource'; 3 | 4 | const typeDefs = gql` 5 | type Query { 6 | dummy: String 7 | } 8 | type Mutation { 9 | upload: String 10 | } 11 | `; 12 | const resolvers = { 13 | Mutation: { 14 | upload(_, __, { dataSources }) { 15 | return dataSources.uploadAPI.postFileToServer({ str: '1234' }); 16 | }, 17 | }, 18 | }; 19 | 20 | const server = new ApolloServer({ 21 | typeDefs, 22 | resolvers, 23 | dataSources: () => { 24 | return { 25 | uploadAPI: new MyDatasource(), 26 | }; 27 | }, 28 | }); 29 | const port = 3001; 30 | server.listen(port).then(({ url }) => console.log(`🚀 Server ready at ${url}`)); 31 | -------------------------------------------------------------------------------- /src/stackoverflow/63181608/uploadServer.ts: -------------------------------------------------------------------------------- 1 | import multer from 'multer'; 2 | import express from 'express'; 3 | import path from 'path'; 4 | 5 | const upload = multer({ dest: path.resolve(__dirname, 'uploads/') }); 6 | const app = express(); 7 | const port = 3000; 8 | 9 | app.post('/upload', upload.single('file'), (req, res) => { 10 | console.log(req.file); 11 | console.log(req.body); 12 | res.sendStatus(200); 13 | }); 14 | 15 | app.listen(port, () => { 16 | console.log(`upload server is listening on http://localhost:${port}`); 17 | }); 18 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/app.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import schema from './typedefs'; 3 | import resolvers from './resolvers'; 4 | import { mergeSchemas } from 'graphql-tools'; 5 | 6 | const mergedSchema = mergeSchemas({ 7 | schemas: schema, 8 | }); 9 | 10 | const server = new ApolloServer({ schema: mergedSchema, resolvers }); 11 | const port = 3000; 12 | server.listen(port).then(({ url }) => console.log(`🚀 Server ready at ${url}`)); 13 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/resolvers/bookResolver.ts: -------------------------------------------------------------------------------- 1 | const Resolvers = { 2 | Query: { 3 | books: (_, args) => [{ name: 'jestjs' }, { name: 'js' }], 4 | }, 5 | }; 6 | 7 | export default Resolvers; 8 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import UserResolvers from './userResolver'; 2 | import BookResolvers from './bookResolver'; 3 | 4 | const Resolvers = [UserResolvers, BookResolvers]; 5 | 6 | export default Resolvers; 7 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/resolvers/userResolver.ts: -------------------------------------------------------------------------------- 1 | const Resolvers = { 2 | Query: { 3 | whoami: (_, args, { models }) => ({ name: 'teresa teng' }), 4 | }, 5 | }; 6 | 7 | export default Resolvers; 8 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/typedefs/bookSchema.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server-express'; 2 | 3 | const BookSchema = gql` 4 | type Query { 5 | books: [Book]! 6 | } 7 | type Book { 8 | name: String! 9 | } 10 | `; 11 | 12 | export default BookSchema; 13 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/typedefs/index.ts: -------------------------------------------------------------------------------- 1 | import userTypeDefs from './userSchema'; 2 | import bookTypeDefs from './bookSchema'; 3 | import { gql, makeExecutableSchema } from 'apollo-server-express'; 4 | import resolvers from '../resolvers'; 5 | 6 | const defaultTypeDefs = gql` 7 | type Query { 8 | _: Boolean 9 | } 10 | type Mutation { 11 | _: Boolean 12 | } 13 | `; 14 | 15 | const [UserResolvers, BookResolvers] = resolvers; 16 | const userSchema = makeExecutableSchema({ typeDefs: userTypeDefs, resolvers: UserResolvers }); 17 | const bookSchema = makeExecutableSchema({ typeDefs: bookTypeDefs, resolvers: BookResolvers }); 18 | const defaultSchema = makeExecutableSchema({ typeDefs: defaultTypeDefs }); 19 | 20 | export default [userSchema, bookSchema, defaultSchema]; 21 | -------------------------------------------------------------------------------- /src/stackoverflow/63889126/typedefs/userSchema.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server-express'; 2 | 3 | const UserSchema = gql` 4 | type Query { 5 | whoami: User 6 | } 7 | type User { 8 | name: String! 9 | } 10 | `; 11 | 12 | export default UserSchema; 13 | -------------------------------------------------------------------------------- /src/stackoverflow/64557654/server.js: -------------------------------------------------------------------------------- 1 | import apolloServerPluginResponseCache from 'apollo-server-plugin-response-cache'; 2 | import { ApolloServer, gql } from 'apollo-server'; 3 | import { RedisCache } from 'apollo-server-cache-redis'; 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | exapi(param1: String, param2: Boolean): String 8 | } 9 | `; 10 | const resolvers = { 11 | Query: { 12 | exapi: (_, { param1, param2 }) => 'teresa teng', 13 | }, 14 | }; 15 | 16 | const cache = new RedisCache({ host: 'localhost', port: 6379 }); 17 | 18 | const server = new ApolloServer({ 19 | introspection: true, 20 | playground: true, 21 | subscriptions: false, 22 | typeDefs, 23 | resolvers, 24 | cacheControl: { 25 | defaultMaxAge: 60, 26 | }, 27 | plugins: [ 28 | apolloServerPluginResponseCache({ 29 | cache, 30 | shouldWriteToCache: (requestContext) => { 31 | console.log(requestContext.document.definitions[0].selectionSet.selections[0].arguments); 32 | return true; 33 | }, 34 | }), 35 | ], 36 | }); 37 | server.listen().then(({ url }) => console.log(`🚀 Server ready at ${url}`)); 38 | -------------------------------------------------------------------------------- /src/stackoverflow/66035127/app.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { ApolloServer, gql } from 'apollo-server-express'; 3 | const app = express(); 4 | 5 | const typeDefs = gql` 6 | type Query { 7 | hi: String 8 | } 9 | `; 10 | 11 | const resolvers = { 12 | Query: { 13 | hi: () => 'hi', 14 | }, 15 | }; 16 | 17 | const server = new ApolloServer({ typeDefs, resolvers }); 18 | server.applyMiddleware({ app, path: '/graphql' }); 19 | app.listen(8080, () => console.log('Apollo server started at http://localhost:8080')); 20 | 21 | // For testing 22 | // curl -i -H 'Content-Type: application/json' -X POST -d '{"query": "query {hi}"}' http://localhost:8080/graphql 23 | -------------------------------------------------------------------------------- /src/stackoverflow/66872082/queries.ts: -------------------------------------------------------------------------------- 1 | import { gql } from '@apollo/client'; 2 | 3 | const GET_ROCKETS = gql` 4 | query GetRockets { 5 | rockets { 6 | id 7 | name 8 | } 9 | } 10 | `; 11 | const GET_ROCKET = gql` 12 | query GetRocket($id: ID!) { 13 | rocket(id: $id) { 14 | mass { 15 | kg 16 | lb 17 | } 18 | name 19 | } 20 | } 21 | `; 22 | export { GET_ROCKETS, GET_ROCKET }; 23 | -------------------------------------------------------------------------------- /src/stackoverflow/68629868/errorLink.test.ts: -------------------------------------------------------------------------------- 1 | import { ApolloLink, execute, Observable } from '@apollo/client'; 2 | import { gql } from 'apollo-server-express'; 3 | import { errorLink } from './errorLink'; 4 | 5 | const MockQuery = gql` 6 | query { 7 | foo 8 | } 9 | `; 10 | 11 | describe('68629868', () => { 12 | test('should pass', (done) => { 13 | expect.assertions(1); 14 | const mockLink = new ApolloLink((operation) => 15 | Observable.of({ 16 | errors: [ 17 | { 18 | message: 'resolver blew up', 19 | }, 20 | ], 21 | } as any), 22 | ); 23 | 24 | const link = errorLink.concat(mockLink); 25 | execute(link, { query: MockQuery }).subscribe((result) => { 26 | expect(result.errors![0].message).toBe('resolver blew up'); 27 | done(); 28 | }); 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /src/stackoverflow/68629868/errorLink.ts: -------------------------------------------------------------------------------- 1 | import { onError } from '@apollo/client/link/error'; 2 | 3 | type ErrorResponse = any; 4 | 5 | export const errorLink = onError(({ response, graphQLErrors, networkError, operation }: ErrorResponse) => { 6 | console.log('An Error Occurred'); 7 | console.log('graphQLErrors: ', graphQLErrors); 8 | }); 9 | -------------------------------------------------------------------------------- /src/stackoverflow/69600434/index.js: -------------------------------------------------------------------------------- 1 | const { ApolloServer } = require('apollo-server'); 2 | const { makeExecutableSchema } = require('@graphql-tools/schema'); 3 | const { applyMiddleware } = require('graphql-middleware'); 4 | 5 | // Minimal example middleware (before & after) 6 | const beepMiddleware = { 7 | Query: { 8 | hello: async (resolve, parent, args, context, info) => { 9 | // You can use middleware to override arguments 10 | const argsWithDefault = { name: 'Bob', ...args }; 11 | const result = await resolve(parent, argsWithDefault, context, info); 12 | // Or change the returned values of resolvers 13 | return result.replace(/Trump/g, 'beep'); 14 | }, 15 | tours: async (resolve, parent, args, context, info) => { 16 | const result = await resolve(parent, args, context, info); 17 | return result.concat([4]); 18 | }, 19 | }, 20 | }; 21 | 22 | const typeDefs = ` 23 | type Query { 24 | hello(name: String): String 25 | tours: [Int]! 26 | } 27 | `; 28 | const resolvers = { 29 | Query: { 30 | hello: (parent, { name }, context) => `Hello ${name ? name : 'world'}!`, 31 | tours: () => [1, 2, 3], 32 | }, 33 | }; 34 | 35 | const schema = makeExecutableSchema({ typeDefs, resolvers }); 36 | 37 | const schemaWithMiddleware = applyMiddleware(schema, beepMiddleware); 38 | 39 | const server = new ApolloServer({ 40 | schema: schemaWithMiddleware, 41 | }); 42 | 43 | server.listen({ port: 8008 }).then(() => console.log('Server started at http://localhost:8008')); 44 | -------------------------------------------------------------------------------- /src/subscriptions/auth.ts: -------------------------------------------------------------------------------- 1 | import { Request } from 'express'; 2 | 3 | function validateToken(req: Request | string): string { 4 | let token: string; 5 | if (typeof req === 'string') { 6 | token = req; 7 | } else { 8 | token = req.headers.authorization || ''; 9 | } 10 | const parts = token.split(' '); 11 | const bearer = parts[0]; 12 | const credential = parts[1]; 13 | if (/^Bearer$/i.test(bearer) && credential) { 14 | return credential; 15 | } 16 | throw new Error('Missing auth token!'); 17 | } 18 | 19 | export { validateToken }; 20 | -------------------------------------------------------------------------------- /src/subscriptions/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | HTTP_SERVER: { 3 | PORT: process.env.PORT || 3000, 4 | }, 5 | }; 6 | 7 | export { config }; 8 | -------------------------------------------------------------------------------- /src/subscriptions/connectors/Base.ts: -------------------------------------------------------------------------------- 1 | class BaseConnector { 2 | protected datasource: Datasource; 3 | constructor(datasource: Datasource) { 4 | this.datasource = datasource; 5 | } 6 | } 7 | 8 | export { BaseConnector }; 9 | -------------------------------------------------------------------------------- /src/subscriptions/connectors/Location.ts: -------------------------------------------------------------------------------- 1 | import { BaseConnector } from './Base'; 2 | import { IMemoryDB, ILocation } from '../datasources/memoryDB'; 3 | 4 | class LocationConnector extends BaseConnector { 5 | constructor(datasource: Datasource) { 6 | super(datasource); 7 | } 8 | 9 | public findLocationsByOrgId(id: string) { 10 | return this.datasource.locations.filter((location) => location.orgId === id); 11 | } 12 | 13 | public findLocationIdsByOrgId(id: string) { 14 | return this.findLocationsByOrgId(id).map((loc) => loc.id); 15 | } 16 | 17 | public findAll(): ILocation[] { 18 | return this.datasource.locations; 19 | } 20 | } 21 | 22 | export { LocationConnector }; 23 | -------------------------------------------------------------------------------- /src/subscriptions/connectors/index.ts: -------------------------------------------------------------------------------- 1 | import { LocationConnector } from './Location'; 2 | import { UserConnector } from './User'; 3 | import { TemplateConnector } from './Template'; 4 | import { IMemoryDB } from '../datasources/memoryDB'; 5 | 6 | interface IConnectors { 7 | locationConnector: LocationConnector; 8 | userConnector: UserConnector; 9 | templateConnector: TemplateConnector; 10 | } 11 | 12 | export { IConnectors, LocationConnector, UserConnector, TemplateConnector }; 13 | -------------------------------------------------------------------------------- /src/subscriptions/credentials.ts: -------------------------------------------------------------------------------- 1 | import { ICredentials, loadEnv } from './env'; 2 | 3 | loadEnv(); 4 | 5 | export const credentials: ICredentials = { 6 | SQL_HOST: process.env.SQL_HOST || '', 7 | SQL_PORT: process.env.SQL_PORT || '', 8 | SQL_DATABASE: process.env.SQL_DATABASE || '', 9 | SQL_USER: process.env.SQL_USER || '', 10 | SQL_PASSWORD: process.env.SQL_PASSWORD || '', 11 | APOLLO_ENGINE_API_KEY: process.env.APOLLO_ENGINE_API_KEY || '', 12 | }; 13 | 14 | export { ICredentials }; 15 | -------------------------------------------------------------------------------- /src/subscriptions/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | const interpreter = 'ts-node'; 2 | const script = './src/subscriptions/main.ts'; 3 | const name = 'graphql-subscription-backend-server-instance'; 4 | 5 | module.exports = { 6 | apps: [ 7 | { name: `${name}-1`, interpreter, script, port: 3001 }, 8 | { name: `${name}-2`, interpreter, script, port: 3002 }, 9 | { name: `${name}-3`, interpreter, script, port: 3003 }, 10 | { name: `${name}-4`, interpreter, script, port: 3004 }, 11 | ], 12 | }; 13 | -------------------------------------------------------------------------------- /src/subscriptions/env.ts: -------------------------------------------------------------------------------- 1 | import dotenv, { DotenvConfigOutput, DotenvParseOutput } from 'dotenv'; 2 | import path from 'path'; 3 | 4 | interface ICredentials { 5 | SQL_HOST: string; 6 | SQL_PORT: string; 7 | SQL_DATABASE: string; 8 | SQL_USER: string; 9 | SQL_PASSWORD: string; 10 | APOLLO_ENGINE_API_KEY: string; 11 | } 12 | 13 | type EnvVars = DotenvParseOutput & ICredentials; 14 | 15 | function loadEnv(): EnvVars { 16 | const dotenvConfigOutput: DotenvConfigOutput = dotenv.config({ path: path.resolve(__dirname, './.env') }); 17 | if (dotenvConfigOutput.error) { 18 | throw dotenvConfigOutput.error; 19 | } 20 | const envVars: EnvVars = { 21 | SQL_HOST: '', 22 | SQL_PORT: '', 23 | SQL_DATABASE: '', 24 | SQL_USER: '', 25 | SQL_PASSWORD: '', 26 | APOLLO_ENGINE_API_KEY: '', 27 | ...dotenvConfigOutput.parsed, 28 | }; 29 | return envVars; 30 | } 31 | 32 | export { loadEnv, ICredentials, EnvVars }; 33 | -------------------------------------------------------------------------------- /src/subscriptions/main.ts: -------------------------------------------------------------------------------- 1 | import { createServer, IServerOptions } from './server'; 2 | import { config } from './config'; 3 | 4 | async function main() { 5 | const options: IServerOptions = { 6 | PORT: config.HTTP_SERVER.PORT, 7 | }; 8 | await createServer(options); 9 | } 10 | 11 | if (require.main === module) { 12 | main(); 13 | } 14 | -------------------------------------------------------------------------------- /src/subscriptions/models/Base.ts: -------------------------------------------------------------------------------- 1 | type ID = string; 2 | 3 | type Omit = Pick>; 4 | 5 | interface IBaseModel { 6 | id: ID; 7 | } 8 | 9 | export { ID, Omit, IBaseModel }; 10 | -------------------------------------------------------------------------------- /src/subscriptions/models/CommonResponse.ts: -------------------------------------------------------------------------------- 1 | interface ICommonResponse { 2 | success: boolean; 3 | message: string; 4 | payload?: any; 5 | } 6 | 7 | export { ICommonResponse }; 8 | -------------------------------------------------------------------------------- /src/subscriptions/pubsub.ts: -------------------------------------------------------------------------------- 1 | import { PubSub, withFilter } from 'apollo-server'; 2 | import { PostgresPubSub } from 'graphql-postgres-subscriptions'; 3 | import { Client } from 'pg'; 4 | import { credentials } from './credentials'; 5 | 6 | const pubsub = new PubSub(); 7 | 8 | enum TriggerNameType { 9 | TEMPLATE_ADDED = 'TEMPLATE_ADDED', 10 | } 11 | 12 | async function createPostgresPubSub(): Promise { 13 | const client = new Client({ 14 | host: credentials.SQL_HOST, 15 | port: Number.parseInt(credentials.SQL_PORT, 10), 16 | database: credentials.SQL_DATABASE, 17 | user: credentials.SQL_USER, 18 | password: credentials.SQL_PASSWORD, 19 | }); 20 | await client.connect(); 21 | const postgresPubSub = new PostgresPubSub({ client, commonMessageHandler }); 22 | return postgresPubSub; 23 | } 24 | 25 | function commonMessageHandler(message) { 26 | // console.log('commonMessageHandler: ', JSON.stringify(message)); 27 | return message; 28 | } 29 | 30 | export { pubsub, PubSub, TriggerNameType, withFilter, createPostgresPubSub }; 31 | -------------------------------------------------------------------------------- /src/subscriptions/readme.md: -------------------------------------------------------------------------------- 1 | # subscriptions 2 | 3 | ```bash 4 | npx ts-node ./src/subscriptions/main.ts 5 | ``` 6 | 7 | watch mode: 8 | 9 | ```bash 10 | npm run watch -- ./src/subscriptions/main.ts 11 | ``` 12 | 13 | `.env`: 14 | 15 | ```bash 16 | SQL_HOST=127.0.0.1 17 | SQL_PORT=5431 18 | SQL_DATABASE=nodejs-pg-knex-samples 19 | SQL_USER=sampleadmin 20 | SQL_PASSWORD=samplepass 21 | ``` 22 | -------------------------------------------------------------------------------- /src/subscriptions/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type Template { 5 | id: ID! 6 | name: String! 7 | shareLocationIds: [ID!] 8 | } 9 | 10 | input EditTemplateInput { 11 | id: ID! 12 | name: String 13 | shareLocationIds: [ID!] 14 | } 15 | 16 | input AddTemplateInput { 17 | name: String! 18 | shareLocationIds: [ID!] 19 | } 20 | 21 | type Location { 22 | id: ID! 23 | name: String! 24 | orgId: ID! 25 | } 26 | 27 | type CommonResponse { 28 | success: Boolean! 29 | message: String 30 | } 31 | 32 | type Query { 33 | templates: [Template!] 34 | templateById(id: ID!): Template 35 | locations: [Location!] 36 | locationsByOrgId(id: ID!): [Location!] 37 | } 38 | 39 | type Mutation { 40 | editTemplate(templateInput: EditTemplateInput!): CommonResponse 41 | addTemplate(templateInput: AddTemplateInput!): CommonResponse 42 | } 43 | 44 | type Subscription { 45 | templateAdded: Template 46 | } 47 | 48 | schema { 49 | query: Query 50 | mutation: Mutation 51 | subscription: Subscription 52 | } 53 | `; 54 | 55 | export { typeDefs }; 56 | -------------------------------------------------------------------------------- /src/subscriptions/util.ts: -------------------------------------------------------------------------------- 1 | function intersection(arr1: Array, arr2: Array): Array { 2 | return arr1.filter((value) => arr2.indexOf(value) !== -1); 3 | } 4 | 5 | export { intersection }; 6 | -------------------------------------------------------------------------------- /src/upload/__tests__/__snapshots__/server.integration.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`upload should get file size limit error 1`] = ` 4 | Object { 5 | "data": null, 6 | "errors": Array [ 7 | Object { 8 | "extensions": Object { 9 | "code": "INTERNAL_SERVER_ERROR", 10 | "exception": Object { 11 | "message": "File truncated as it exceeds the size limit.", 12 | }, 13 | }, 14 | "locations": Array [ 15 | Object { 16 | "column": 13, 17 | "line": 3, 18 | }, 19 | ], 20 | "message": "File truncated as it exceeds the size limit.", 21 | "path": Array [ 22 | "singleUpload", 23 | ], 24 | }, 25 | ], 26 | } 27 | `; 28 | 29 | exports[`upload should upload file correctly with test server 1`] = ` 30 | Object { 31 | "data": Object { 32 | "singleUpload": Object { 33 | "code": 0, 34 | "message": "", 35 | }, 36 | }, 37 | } 38 | `; 39 | -------------------------------------------------------------------------------- /src/upload/__tests__/mutations.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | export const singleUpload = gql` 4 | mutation($file: Upload!) { 5 | singleUpload(file: $file) { 6 | code 7 | message 8 | } 9 | } 10 | `; 11 | -------------------------------------------------------------------------------- /src/upload/main.ts: -------------------------------------------------------------------------------- 1 | import { createApolloServer } from './server'; 2 | (async function main() { 3 | const server = await createApolloServer(); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/upload/resolvers.ts: -------------------------------------------------------------------------------- 1 | import { IResolvers } from 'apollo-server'; 2 | import fs, { ReadStream } from 'fs'; 3 | import path from 'path'; 4 | import { logger } from '../util'; 5 | 6 | interface ICommonResponse { 7 | code: number; 8 | message: string; 9 | } 10 | 11 | const resolvers: IResolvers = { 12 | Query: { 13 | uploads: (_, args) => { 14 | return ''; 15 | }, 16 | }, 17 | Mutation: { 18 | singleUpload: async (_, { file }): Promise => { 19 | // DeprecationWarning: File upload property ‘stream’ is deprecated. Use ‘createReadStream()’ instead 20 | const { filename, mimetype, encoding, createReadStream } = await file; 21 | const uploadPath = path.resolve(__dirname, `./storage/${filename}`); 22 | const w = fs.createWriteStream(uploadPath); 23 | const stream: ReadStream = createReadStream(); 24 | const response: ICommonResponse = { code: 0, message: '' }; 25 | return new Promise((resolve, reject) => { 26 | stream 27 | .on('error', (error) => { 28 | console.error(error); 29 | fs.unlinkSync(uploadPath); 30 | reject(error); 31 | }) 32 | .pipe(w) 33 | .on('error', (error) => { 34 | logger.error(error); 35 | response.code = 1; 36 | response.message = error.message; 37 | resolve(response); 38 | }) 39 | .on('finish', () => resolve(response)); 40 | }); 41 | }, 42 | }, 43 | }; 44 | 45 | export { resolvers }; 46 | -------------------------------------------------------------------------------- /src/upload/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer } from 'apollo-server'; 2 | import { typeDefs } from './typeDefs'; 3 | import { resolvers } from './resolvers'; 4 | import { ApolloServerBase, FileUploadOptions } from 'apollo-server-core'; 5 | 6 | async function createApolloServer(): Promise { 7 | const fileUploadOptions: FileUploadOptions = { 8 | // 50KB 9 | maxFileSize: 1024 * 50, 10 | }; 11 | const server = new ApolloServer({ 12 | uploads: fileUploadOptions, 13 | typeDefs, 14 | resolvers, 15 | }); 16 | 17 | const { url } = await server.listen(); 18 | console.log(`🚀 Server ready at ${url}`); 19 | return server; 20 | } 21 | 22 | export { createApolloServer }; 23 | -------------------------------------------------------------------------------- /src/upload/storage/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/upload/storage/.DS_Store -------------------------------------------------------------------------------- /src/upload/storage/a.txt: -------------------------------------------------------------------------------- 1 | a -------------------------------------------------------------------------------- /src/upload/storage/b.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrdulin/apollo-graphql-tutorial/db127c2033c628016a6be64047f634e98981b860/src/upload/storage/b.txt -------------------------------------------------------------------------------- /src/upload/typeDefs.ts: -------------------------------------------------------------------------------- 1 | import { gql } from 'apollo-server'; 2 | 3 | const typeDefs = gql` 4 | type File { 5 | filename: String! 6 | mimetype: String! 7 | encoding: String! 8 | } 9 | 10 | type CommonResponse { 11 | code: Int! 12 | message: String! 13 | } 14 | 15 | type Query { 16 | uploads: [File] 17 | } 18 | 19 | type Mutation { 20 | singleUpload(file: Upload!): CommonResponse! 21 | } 22 | `; 23 | 24 | export { typeDefs }; 25 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { createLogger } from 'dl-toolkits'; 2 | 3 | const logger = createLogger({ serviceName: 'apollo-graphql-tutorial' }); 4 | 5 | function sleep(ms: number, verbose?: boolean): Promise { 6 | if (verbose) { 7 | const unit = 1000; 8 | logger.info(`start the timer...${ms / unit}s`); 9 | const intervalId = setInterval(() => { 10 | ms -= unit; 11 | if (ms > 0) { 12 | logger.info(`${ms / unit}s`); 13 | } else { 14 | logger.info('timer end'); 15 | clearInterval(intervalId); 16 | } 17 | }, unit); 18 | } 19 | return new Promise((resolve) => setTimeout(resolve, ms)); 20 | } 21 | export { logger, sleep }; 22 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended", 5 | "tslint-config-prettier" 6 | ], 7 | "rules": { 8 | "quotemark": false, 9 | "no-console": false, 10 | "trailing-comma": false, 11 | "ordered-imports": false, 12 | "object-literal-sort-keys": false, 13 | "arrow-parens": false, 14 | "interface-over-type-literal": false, 15 | "interface-name": false 16 | }, 17 | "jsRules": true 18 | } --------------------------------------------------------------------------------