├── .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 |
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 | }
--------------------------------------------------------------------------------