├── .gitignore ├── README.md ├── codegen.yml ├── config ├── development.env ├── production.env └── testing.env ├── index.d.ts ├── jest.config.js ├── package.json ├── src ├── __generated__ │ └── generated-types.ts ├── __tests__ │ ├── __snapshots__ │ │ └── integration.test.ts.snap │ ├── integration.test.ts │ └── utils │ │ ├── queries.ts │ │ └── server.ts ├── database │ ├── config.ts │ ├── knexfile.ts │ ├── migrations │ │ └── 20200522223609_initial.ts │ ├── models │ │ ├── Pet.ts │ │ ├── User.ts │ │ └── index.ts │ └── seeds │ │ └── baseSeed.js ├── index.ts ├── schema │ ├── graphql │ │ └── schema.gql │ ├── index.ts │ └── resolvers │ │ ├── index.ts │ │ ├── pet.ts │ │ └── user.ts └── utils │ └── loaders.ts ├── tsconfig.json ├── webpack.config.js ├── yarn-error.log └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /mock.md 2 | node_modules 3 | package-lock.json 4 | dist 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # GraphQL Typescript and PostgreSQL API 3 | 4 | This repo is part of my [post](https://dev.to/wonder2210/graphql-typescript-postgresql-api-271g) intended to show how to build an API using PostgreSQL and typescript 5 | 6 | ## Database 7 | 8 | Create two databases on PostgreSQL called, `post` and `post-test`, and add your database credentials to the file `src/database/config.ts` 9 | in case you might like name the databases with other name in that same file you can rename it 10 | ## Run Locally 11 | 12 | Clone the project 13 | 14 | ```bash 15 | git clone https://github.com/Wonder2210/graphql-typescript-pg-server.git 16 | ``` 17 | 18 | Go to the project directory 19 | 20 | ```bash 21 | cd graphql-typescript-pg-server 22 | ``` 23 | 24 | Install dependencies 25 | 26 | ```bash 27 | yarn install 28 | ``` 29 | 30 | Migrate Database 31 | 32 | ```bash 33 | yarn migrate:up 34 | ``` 35 | 36 | Start the server 37 | 38 | ```bash 39 | yarn dev 40 | ``` 41 | 42 | 43 | ## Running Tests 44 | 45 | To run tests, run the following command 46 | 47 | ```bash 48 | yarn test:integration 49 | ``` 50 | 51 | -------------------------------------------------------------------------------- /codegen.yml: -------------------------------------------------------------------------------- 1 | overwrite: true 2 | schema: "http://localhost:3000/graphql" 3 | documents: null 4 | generates: 5 | src/__generated__/generated-types.ts: 6 | config: 7 | mappers: 8 | User:'./src/database/User.ts' 9 | UpdateUserInput:'./src/database/User.ts' 10 | Pet:'./src/database/Pet.ts' 11 | plugins: 12 | - "typescript" 13 | - "typescript-resolvers" 14 | -------------------------------------------------------------------------------- /config/development.env: -------------------------------------------------------------------------------- 1 | DATABASE=development -------------------------------------------------------------------------------- /config/production.env: -------------------------------------------------------------------------------- 1 | DATABASE=production -------------------------------------------------------------------------------- /config/testing.env: -------------------------------------------------------------------------------- 1 | DATABASE=testing -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.graphql' { 2 | import { DocumentNode } from 'graphql' 3 | const Schema: DocumentNode 4 | 5 | export = Schema 6 | } 7 | declare module '*.gql' { 8 | import { DocumentNode } from 'graphql' 9 | const Schema: DocumentNode 10 | 11 | export = Schema 12 | } 13 | 14 | declare module "knex-cleaner" -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testPathIgnorePatterns: ["/node_modules"], 3 | testMatch: ["**/?(*.)+(spec|test).[jt]s?(x)"], 4 | transform: { 5 | "^.+\\.(j|t)sx?$": ["ts-jest"], 6 | "\\.(gql|graphql)$": ["@jagi/jest-transform-graphql"], 7 | }, 8 | moduleFileExtensions: ["ts", "tsx", "js", "gql"], 9 | }; 10 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Graphql-typescript-postgresql", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "concurrently \" nodemon ./dist/bundle.js \" \" webpack --watch --config webpack.config.js\" ", 8 | "build": "webpack", 9 | "test:integration": "jest /src -c jest.config.js --forceExit ", 10 | "start": "node /dist/bundle.js", 11 | "migrate:up": "knex --knexfile ./src/database/knexfile.ts migrate:latest ", 12 | "test:migrate:up": "knex --knexfile ./src/database/knexfile.ts migrate:latest --env testing", 13 | "test:migrate:down": "knex --knexfile ./src/database/knexfile.ts migrate:down --env testing", 14 | "generate:types": "graphql-codegen --config codegen.yml", 15 | "test:seed-db": "knex --knexfile ./src/database/knexfile.ts seed:run --env testing" 16 | }, 17 | "dependencies": { 18 | 19 | "apollo-server-express": "^2.16.1", 20 | "apollo-server-testing": "^2.16.1", 21 | 22 | "body-parser": "^1.19.0", 23 | "dataloader": "^2.0.0", 24 | "express": "^4.17.1", 25 | "graphql": "^15.0.0", 26 | "graphql-tools": "^6.0.0", 27 | "knex": "^0.21.1", 28 | "objection": "^2.1.3", 29 | "pg": "^8.2.0" 30 | }, 31 | "devDependencies": { 32 | "@graphql-codegen/cli": "1.14.0", 33 | "@graphql-codegen/typescript": "1.14.0", 34 | "@graphql-codegen/typescript-operations": "^1.14.0", 35 | "@graphql-codegen/typescript-resolvers": "1.14.0", 36 | "@jagi/jest-transform-graphql": "^1.0.2", 37 | "@types/express": "^4.17.6", 38 | "@types/graphql": "^14.5.0", 39 | "@types/jest": "^26.0.3", 40 | "@types/mock-knex": "^0.4.3", 41 | "@types/node": "^14.0.1", 42 | "awesome-typescript-loader": "^5.2.1", 43 | "concurrently": "^5.2.0", 44 | "graphql-tag": "^2.10.3", 45 | "jest": "^26.2.1", 46 | "knex-cleaner": "^1.3.1", 47 | "nodemon": "^2.0.4", 48 | "ts-jest": "^26.1.1", 49 | "ts-node": "^8.10.1", 50 | "typescript": "^3.9.2", 51 | "webpack": "^4.43.0", 52 | "webpack-cli": "^3.3.11", 53 | "webpack-node-externals": "^1.7.2" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/__generated__/generated-types.ts: -------------------------------------------------------------------------------- 1 | import { GraphQLResolveInfo } from 'graphql'; 2 | export type Maybe = T | null; 3 | export type RequireFields = { [X in Exclude]?: T[X] } & { [P in K]-?: NonNullable }; 4 | /** All built-in and custom scalars, mapped to their actual values */ 5 | export type Scalars = { 6 | ID: string; 7 | String: string; 8 | Boolean: boolean; 9 | Int: number; 10 | Float: number; 11 | }; 12 | 13 | export enum Species { 14 | Birds = 'BIRDS', 15 | Fish = 'FISH', 16 | Mammals = 'MAMMALS', 17 | Reptiles = 'REPTILES' 18 | } 19 | 20 | export type User = { 21 | __typename?: 'User'; 22 | id: Scalars['Int']; 23 | full_name?: Maybe; 24 | country_code?: Maybe; 25 | created_at?: Maybe; 26 | pets?: Maybe>>; 27 | }; 28 | 29 | export type Pet = { 30 | __typename?: 'Pet'; 31 | id: Scalars['Int']; 32 | name?: Maybe; 33 | owner_id: Scalars['Int']; 34 | specie?: Maybe; 35 | created_at?: Maybe; 36 | owner?: Maybe; 37 | }; 38 | 39 | export type CreateUserInput = { 40 | full_name: Scalars['String']; 41 | country_code: Scalars['String']; 42 | }; 43 | 44 | export type CreatePetInput = { 45 | name: Scalars['String']; 46 | owner_id: Scalars['Int']; 47 | specie: Species; 48 | }; 49 | 50 | export type UpdateUserInput = { 51 | id: Scalars['Int']; 52 | full_name?: Maybe; 53 | country_code?: Maybe; 54 | }; 55 | 56 | export type UpdatePetInput = { 57 | id: Scalars['Int']; 58 | name: Scalars['String']; 59 | }; 60 | 61 | export type Query = { 62 | __typename?: 'Query'; 63 | pets?: Maybe>>; 64 | users?: Maybe>>; 65 | user?: Maybe; 66 | pet?: Maybe; 67 | }; 68 | 69 | 70 | export type QueryUserArgs = { 71 | id: Scalars['Int']; 72 | }; 73 | 74 | 75 | export type QueryPetArgs = { 76 | id: Scalars['Int']; 77 | }; 78 | 79 | export type Mutation = { 80 | __typename?: 'Mutation'; 81 | createPet?: Maybe; 82 | createUser?: Maybe; 83 | deletePet?: Maybe; 84 | deleteUser?: Maybe; 85 | updatePet?: Maybe; 86 | updateUser?: Maybe; 87 | }; 88 | 89 | 90 | export type MutationCreatePetArgs = { 91 | pet: CreatePetInput; 92 | }; 93 | 94 | 95 | export type MutationCreateUserArgs = { 96 | user: CreateUserInput; 97 | }; 98 | 99 | 100 | export type MutationDeletePetArgs = { 101 | id: Scalars['Int']; 102 | }; 103 | 104 | 105 | export type MutationDeleteUserArgs = { 106 | id: Scalars['Int']; 107 | }; 108 | 109 | 110 | export type MutationUpdatePetArgs = { 111 | pet: UpdatePetInput; 112 | }; 113 | 114 | 115 | export type MutationUpdateUserArgs = { 116 | user: UpdateUserInput; 117 | }; 118 | 119 | 120 | 121 | export type ResolverTypeWrapper = Promise | T; 122 | 123 | 124 | export type StitchingResolver = { 125 | fragment: string; 126 | resolve: ResolverFn; 127 | }; 128 | 129 | export type Resolver = 130 | | ResolverFn 131 | | StitchingResolver; 132 | 133 | export type ResolverFn = ( 134 | parent: TParent, 135 | args: TArgs, 136 | context: TContext, 137 | info: GraphQLResolveInfo 138 | ) => Promise | TResult; 139 | 140 | export type SubscriptionSubscribeFn = ( 141 | parent: TParent, 142 | args: TArgs, 143 | context: TContext, 144 | info: GraphQLResolveInfo 145 | ) => AsyncIterator | Promise>; 146 | 147 | export type SubscriptionResolveFn = ( 148 | parent: TParent, 149 | args: TArgs, 150 | context: TContext, 151 | info: GraphQLResolveInfo 152 | ) => TResult | Promise; 153 | 154 | export interface SubscriptionSubscriberObject { 155 | subscribe: SubscriptionSubscribeFn<{ [key in TKey]: TResult }, TParent, TContext, TArgs>; 156 | resolve?: SubscriptionResolveFn; 157 | } 158 | 159 | export interface SubscriptionResolverObject { 160 | subscribe: SubscriptionSubscribeFn; 161 | resolve: SubscriptionResolveFn; 162 | } 163 | 164 | export type SubscriptionObject = 165 | | SubscriptionSubscriberObject 166 | | SubscriptionResolverObject; 167 | 168 | export type SubscriptionResolver = 169 | | ((...args: any[]) => SubscriptionObject) 170 | | SubscriptionObject; 171 | 172 | export type TypeResolveFn = ( 173 | parent: TParent, 174 | context: TContext, 175 | info: GraphQLResolveInfo 176 | ) => Maybe | Promise>; 177 | 178 | export type isTypeOfResolverFn = (obj: T, info: GraphQLResolveInfo) => boolean | Promise; 179 | 180 | export type NextResolverFn = () => Promise; 181 | 182 | export type DirectiveResolverFn = ( 183 | next: NextResolverFn, 184 | parent: TParent, 185 | args: TArgs, 186 | context: TContext, 187 | info: GraphQLResolveInfo 188 | ) => TResult | Promise; 189 | 190 | /** Mapping between all available schema types and the resolvers types */ 191 | export type ResolversTypes = { 192 | String: ResolverTypeWrapper; 193 | Boolean: ResolverTypeWrapper; 194 | Species: Species; 195 | User: ResolverTypeWrapper; 196 | Int: ResolverTypeWrapper; 197 | Pet: ResolverTypeWrapper; 198 | createUserInput: CreateUserInput; 199 | createPetInput: CreatePetInput; 200 | updateUserInput: UpdateUserInput; 201 | updatePetInput: UpdatePetInput; 202 | Query: ResolverTypeWrapper<{}>; 203 | Mutation: ResolverTypeWrapper<{}>; 204 | }; 205 | 206 | /** Mapping between all available schema types and the resolvers parents */ 207 | export type ResolversParentTypes = { 208 | String: Scalars['String']; 209 | Boolean: Scalars['Boolean']; 210 | Species: Species; 211 | User: User; 212 | Int: Scalars['Int']; 213 | Pet: Pet; 214 | createUserInput: CreateUserInput; 215 | createPetInput: CreatePetInput; 216 | updateUserInput: UpdateUserInput; 217 | updatePetInput: UpdatePetInput; 218 | Query: {}; 219 | Mutation: {}; 220 | }; 221 | 222 | export type UserResolvers = { 223 | id?: Resolver; 224 | full_name?: Resolver, ParentType, ContextType>; 225 | country_code?: Resolver, ParentType, ContextType>; 226 | created_at?: Resolver, ParentType, ContextType>; 227 | pets?: Resolver>>, ParentType, ContextType>; 228 | __isTypeOf?: isTypeOfResolverFn; 229 | }; 230 | 231 | export type PetResolvers = { 232 | id?: Resolver; 233 | name?: Resolver, ParentType, ContextType>; 234 | owner_id?: Resolver; 235 | specie?: Resolver, ParentType, ContextType>; 236 | created_at?: Resolver, ParentType, ContextType>; 237 | owner?: Resolver, ParentType, ContextType>; 238 | __isTypeOf?: isTypeOfResolverFn; 239 | }; 240 | 241 | export type QueryResolvers = { 242 | pets?: Resolver>>, ParentType, ContextType>; 243 | users?: Resolver>>, ParentType, ContextType>; 244 | user?: Resolver, ParentType, ContextType, RequireFields>; 245 | pet?: Resolver, ParentType, ContextType, RequireFields>; 246 | }; 247 | 248 | export type MutationResolvers = { 249 | createPet?: Resolver, ParentType, ContextType, RequireFields>; 250 | createUser?: Resolver, ParentType, ContextType, RequireFields>; 251 | deletePet?: Resolver, ParentType, ContextType, RequireFields>; 252 | deleteUser?: Resolver, ParentType, ContextType, RequireFields>; 253 | updatePet?: Resolver, ParentType, ContextType, RequireFields>; 254 | updateUser?: Resolver, ParentType, ContextType, RequireFields>; 255 | }; 256 | 257 | export type Resolvers = { 258 | User?: UserResolvers; 259 | Pet?: PetResolvers; 260 | Query?: QueryResolvers; 261 | Mutation?: MutationResolvers; 262 | }; 263 | 264 | 265 | /** 266 | * @deprecated 267 | * Use "Resolvers" root object instead. If you wish to get "IResolvers", add "typesPrefix: I" to your config. 268 | */ 269 | export type IResolvers = Resolvers; 270 | -------------------------------------------------------------------------------- /src/__tests__/__snapshots__/integration.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`create pet 1`] = ` 4 | Object { 5 | "data": Object { 6 | "createPet": Object { 7 | "id": 4, 8 | "name": "Optimus", 9 | "specie": "MAMMALS", 10 | }, 11 | }, 12 | "errors": undefined, 13 | "extensions": undefined, 14 | "http": Object { 15 | "headers": Headers { 16 | Symbol(map): Object {}, 17 | }, 18 | }, 19 | } 20 | `; 21 | 22 | exports[`create user 1`] = ` 23 | Object { 24 | "data": Object { 25 | "createUser": Object { 26 | "country_code": "ve", 27 | "full_name": "Wonder", 28 | "id": 4, 29 | }, 30 | }, 31 | "errors": undefined, 32 | "extensions": undefined, 33 | "http": Object { 34 | "headers": Headers { 35 | Symbol(map): Object {}, 36 | }, 37 | }, 38 | } 39 | `; 40 | 41 | exports[`delete user 1`] = ` 42 | Object { 43 | "data": Object { 44 | "deleteUser": "Succesfull deleted", 45 | }, 46 | "errors": undefined, 47 | "extensions": undefined, 48 | "http": Object { 49 | "headers": Headers { 50 | Symbol(map): Object {}, 51 | }, 52 | }, 53 | } 54 | `; 55 | 56 | exports[`query pets 1`] = ` 57 | Object { 58 | "data": Object { 59 | "pets": Array [ 60 | Object { 61 | "id": 1, 62 | "name": "spot", 63 | "owner_id": 1, 64 | "specie": "MAMMALS", 65 | }, 66 | Object { 67 | "id": 2, 68 | "name": "chief", 69 | "owner_id": 2, 70 | "specie": "MAMMALS", 71 | }, 72 | Object { 73 | "id": 3, 74 | "name": "king", 75 | "owner_id": 3, 76 | "specie": "MAMMALS", 77 | }, 78 | ], 79 | }, 80 | "errors": undefined, 81 | "extensions": undefined, 82 | "http": Object { 83 | "headers": Headers { 84 | Symbol(map): Object {}, 85 | }, 86 | }, 87 | } 88 | `; 89 | 90 | exports[`query users 1`] = ` 91 | Object { 92 | "data": Object { 93 | "users": Array [ 94 | Object { 95 | "country_code": "58", 96 | "full_name": "luis", 97 | "id": 1, 98 | }, 99 | Object { 100 | "country_code": "59", 101 | "full_name": "jose", 102 | "id": 2, 103 | }, 104 | Object { 105 | "country_code": "39", 106 | "full_name": "raul", 107 | "id": 3, 108 | }, 109 | ], 110 | }, 111 | "errors": undefined, 112 | "extensions": undefined, 113 | "http": Object { 114 | "headers": Headers { 115 | Symbol(map): Object {}, 116 | }, 117 | }, 118 | } 119 | `; 120 | 121 | exports[`query users and its pets 1`] = ` 122 | Object { 123 | "data": Object { 124 | "users": Array [ 125 | Object { 126 | "country_code": "58", 127 | "full_name": "luis", 128 | "id": 1, 129 | "pets": Array [ 130 | Object { 131 | "id": 1, 132 | "name": "spot", 133 | "specie": "MAMMALS", 134 | }, 135 | ], 136 | }, 137 | Object { 138 | "country_code": "59", 139 | "full_name": "jose", 140 | "id": 2, 141 | "pets": Array [ 142 | Object { 143 | "id": 2, 144 | "name": "chief", 145 | "specie": "MAMMALS", 146 | }, 147 | ], 148 | }, 149 | Object { 150 | "country_code": "39", 151 | "full_name": "raul", 152 | "id": 3, 153 | "pets": Array [ 154 | Object { 155 | "id": 3, 156 | "name": "king", 157 | "specie": "MAMMALS", 158 | }, 159 | ], 160 | }, 161 | ], 162 | }, 163 | "errors": undefined, 164 | "extensions": undefined, 165 | "http": Object { 166 | "headers": Headers { 167 | Symbol(map): Object {}, 168 | }, 169 | }, 170 | } 171 | `; 172 | 173 | exports[`update user 1`] = ` 174 | Object { 175 | "data": Object { 176 | "updateUser": Object { 177 | "country_code": "58", 178 | "full_name": "wilson", 179 | "id": 1, 180 | }, 181 | }, 182 | "errors": undefined, 183 | "extensions": undefined, 184 | "http": Object { 185 | "headers": Headers { 186 | Symbol(map): Object {}, 187 | }, 188 | }, 189 | } 190 | `; 191 | -------------------------------------------------------------------------------- /src/__tests__/integration.test.ts: -------------------------------------------------------------------------------- 1 | import { server, startDb, cleanDb } from "./utils/server"; 2 | import { createTestClient } from "apollo-server-testing"; 3 | import { 4 | GET_USERS, 5 | GET_PETS, 6 | GET_USERS_AND_PETS, 7 | CREATE_USER, 8 | CREATE_PET, 9 | UPDATE_USER, 10 | DELETE_USER, 11 | } from "./utils/queries"; 12 | 13 | beforeAll(() => { 14 | return startDb(); 15 | }); 16 | afterAll(()=>{ 17 | return cleanDb(); 18 | }) 19 | 20 | test("query users", async () => { 21 | const serverTest = server(); 22 | const { query } = createTestClient(serverTest); 23 | 24 | const res = await query({ query: GET_USERS }); 25 | 26 | expect(res).toMatchSnapshot(); 27 | }); 28 | 29 | test("query pets", async () => { 30 | const serverTest = server(); 31 | const { query } = createTestClient(serverTest); 32 | 33 | const res = await query({ query: GET_PETS }); 34 | 35 | expect(res).toMatchSnapshot(); 36 | }); 37 | 38 | test("query users and its pets", async () => { 39 | const serverTest = server(); 40 | const { query } = createTestClient(serverTest); 41 | 42 | const res = await query({ query: GET_USERS_AND_PETS }); 43 | 44 | expect(res).toMatchSnapshot(); 45 | }); 46 | 47 | test("create user ", async () => { 48 | const serverTest = server(); 49 | const { mutate } = createTestClient(serverTest); 50 | 51 | const res = await mutate({ 52 | mutation: CREATE_USER, 53 | variables: { 54 | name: "Wonder", 55 | country_code: "ve", 56 | }, 57 | }); 58 | 59 | expect(res).toMatchSnapshot(); 60 | }); 61 | 62 | test("create pet", async () => { 63 | const serverTest = server(); 64 | const { mutate } = createTestClient(serverTest); 65 | 66 | const res = await mutate({ 67 | mutation: CREATE_PET, 68 | variables: { 69 | name: "Optimus", 70 | owner_id: 1, 71 | specie: "MAMMALS", 72 | }, 73 | }); 74 | 75 | expect(res).toMatchSnapshot(); 76 | }); 77 | 78 | test("update user", async () => { 79 | const serverTest = server(); 80 | const { mutate } = createTestClient(serverTest); 81 | 82 | const res = await mutate({ 83 | mutation: UPDATE_USER, 84 | variables: { 85 | id: 1, 86 | name: "wilson", 87 | }, 88 | }); 89 | 90 | expect(res).toMatchSnapshot(); 91 | }); 92 | 93 | test("delete user", async () => { 94 | const serverTest = server(); 95 | const { mutate } = createTestClient(serverTest); 96 | 97 | const res = await mutate({ 98 | mutation: DELETE_USER, 99 | variables: { 100 | id: 2, 101 | }, 102 | }); 103 | 104 | expect(res).toMatchSnapshot(); 105 | }); 106 | -------------------------------------------------------------------------------- /src/__tests__/utils/queries.ts: -------------------------------------------------------------------------------- 1 | import gql from "graphql-tag"; 2 | 3 | export const GET_USERS = gql` 4 | query getUsers { 5 | users { 6 | id 7 | full_name 8 | country_code 9 | } 10 | } 11 | `; 12 | 13 | export const GET_USERS_AND_PETS = gql` 14 | query getUsersAndItsPets { 15 | users { 16 | id 17 | full_name 18 | country_code 19 | pets { 20 | id 21 | name 22 | specie 23 | } 24 | } 25 | } 26 | `; 27 | 28 | export const GET_PETS = gql` 29 | query getPets { 30 | pets { 31 | id 32 | name 33 | owner_id 34 | specie 35 | } 36 | } 37 | `; 38 | 39 | export const CREATE_USER = gql` 40 | mutation CreateUser($name: String!, $country_code: String!) { 41 | createUser(user: { full_name: $name, country_code: $country_code }) { 42 | id 43 | full_name 44 | country_code 45 | } 46 | } 47 | `; 48 | export const CREATE_PET = gql` 49 | mutation CreatePet($name: String!, $owner_id: Int!, $specie: Species!) { 50 | createPet(pet: { name: $name, owner_id: $owner_id, specie: $specie }) { 51 | id 52 | name 53 | specie 54 | } 55 | } 56 | `; 57 | 58 | export const DELETE_PET = gql` 59 | mutation DeletePet($id: Int!) { 60 | deletePet(id: $id) { 61 | String 62 | } 63 | } 64 | `; 65 | 66 | export const DELETE_USER = gql` 67 | mutation DeleteUser($id: Int!) { 68 | deleteUser(id: $id) 69 | } 70 | `; 71 | 72 | export const UPDATE_USER = gql` 73 | mutation UpdateUser($id: Int!, $name: String, $country_code: String) { 74 | updateUser( 75 | user: { id: $id, full_name: $name, country_code: $country_code } 76 | ) { 77 | id 78 | full_name 79 | country_code 80 | } 81 | } 82 | `; 83 | 84 | export const UPDATE_PET = gql` 85 | mutation UpdatePet($id: Int!, $name: String!) { 86 | updatePet(pet: { id: $id, name: $name }) { 87 | id 88 | name 89 | owner_id 90 | } 91 | } 92 | `; 93 | -------------------------------------------------------------------------------- /src/__tests__/utils/server.ts: -------------------------------------------------------------------------------- 1 | import { ApolloServer, Config } from "apollo-server-express"; 2 | import Knex from "knex"; 3 | import { Model } from "objection"; 4 | import dbconfig from "../../database/config"; 5 | import DataLoader from "dataloader"; 6 | import schema from "../../schema"; 7 | import { Pets, Users } from "../../utils/loaders"; 8 | import dbCleaner from "knex-cleaner"; 9 | 10 | const db = Knex(dbconfig["testing"]); 11 | export const startDb = () => { 12 | 13 | Model.knex(db); 14 | }; 15 | 16 | export const stopDB = () => { 17 | Model.knex().destroy(); 18 | }; 19 | 20 | export const cleanDb= async ()=>{ 21 | const options = { 22 | mode: 'truncate', 23 | restartIdentity: true, 24 | } 25 | return await dbCleaner.clean(db,options); 26 | } 27 | 28 | const config: Config = { 29 | schema: schema, 30 | context: { 31 | loaders: { 32 | users: new DataLoader(Users), 33 | pets: new DataLoader(Pets), 34 | }, 35 | }, 36 | }; 37 | 38 | export const server: () => ApolloServer = () => new ApolloServer(config); 39 | -------------------------------------------------------------------------------- /src/database/config.ts: -------------------------------------------------------------------------------- 1 | const default_config = { 2 | client: "pg", 3 | connection: { 4 | database: "post", 5 | user: "postgres", 6 | password: "root", 7 | }, 8 | pool: { 9 | min: 2, 10 | max: 10, 11 | }, 12 | migrations: { 13 | tableName: "knex_migrations", 14 | directory: "migrations", 15 | }, 16 | timezone: "UTC", 17 | }; 18 | interface KnexConfig { 19 | [key: string]: object; 20 | } 21 | const config: KnexConfig = { 22 | development: { 23 | ...default_config, 24 | }, 25 | testing: { 26 | ...default_config, 27 | connection: { 28 | database: "post-test", 29 | user: "postgres", 30 | password: "root", 31 | }, 32 | pool: { min: 0, max: 10, idleTimeoutMillis: 500 }, 33 | }, 34 | production: { 35 | ...default_config, 36 | }, 37 | }; 38 | 39 | export default config; 40 | -------------------------------------------------------------------------------- /src/database/knexfile.ts: -------------------------------------------------------------------------------- 1 | require("ts-node/register"); 2 | import config from "./config"; 3 | 4 | module.exports = config; 5 | -------------------------------------------------------------------------------- /src/database/migrations/20200522223609_initial.ts: -------------------------------------------------------------------------------- 1 | import * as Knex from "knex"; 2 | 3 | export async function up(knex: Knex): Promise { 4 | return knex.schema 5 | .createTable("users", (table: Knex.CreateTableBuilder) => { 6 | table.increments("id"); 7 | table.string("full_name", 36); 8 | table.string("country_code", 5); 9 | table.timestamps(true, true); 10 | }) 11 | .createTable("pets", (table: Knex.CreateTableBuilder) => { 12 | table.increments("id"); 13 | table.string("name"); 14 | table.integer("owner_id").references("users.id").unsigned().onDelete("CASCADE"); 15 | table.string("specie"); 16 | table.timestamps(true, true); 17 | }); 18 | } 19 | 20 | export async function down(knex: Knex): Promise { 21 | return 22 | } 23 | -------------------------------------------------------------------------------- /src/database/models/Pet.ts: -------------------------------------------------------------------------------- 1 | import {Model} from 'objection'; 2 | import {Species,Maybe} from '../../__generated__/generated-types'; 3 | 4 | import User from './User'; 5 | 6 | class Pet extends Model{ 7 | static tableName = "pets"; 8 | id! : number; 9 | name?: Maybe; 10 | specie?: Maybe; 11 | created_at?:string; 12 | owner_id!:number; 13 | owner?:User; 14 | 15 | static jsonSchema ={ 16 | type:'object', 17 | required:['name'], 18 | 19 | properties:{ 20 | id:{type:'integer'}, 21 | name:{type:'string', min:1, max:255}, 22 | specie:{type:'string',min:1, max:255}, 23 | created_at:{type:'string',min:1, max:255} 24 | } 25 | }; 26 | 27 | static relationMappings=()=>({ 28 | owner:{ 29 | relation:Model.BelongsToOneRelation, 30 | modelClass:User, 31 | join: { 32 | from: 'pets.owner_id', 33 | to: 'users.id', 34 | } 35 | } 36 | }); 37 | 38 | 39 | }; 40 | 41 | export default Pet; -------------------------------------------------------------------------------- /src/database/models/User.ts: -------------------------------------------------------------------------------- 1 | import {Model} from 'objection'; 2 | import {Maybe} from '../../__generated__/generated-types'; 3 | import Pet from './Pet'; 4 | 5 | 6 | 7 | class User extends Model{ 8 | static tableName = "users"; 9 | id! : number; 10 | full_name!: Maybe; 11 | country_code! : Maybe; 12 | created_at?:string; 13 | pets?:Pet[]; 14 | 15 | static jsonSchema = { 16 | type:'object', 17 | required:['full_name'], 18 | 19 | properties:{ 20 | id: { type:'integer'}, 21 | full_name:{type :'string', min:1, max :255}, 22 | country_code:{type :'string', min:1, max :255}, 23 | created_at:{type :'string', min:1, max :255} 24 | } 25 | } 26 | 27 | static relationMappings =()=>({ 28 | pets: { 29 | relation: Model.HasManyRelation, 30 | modelClass: Pet, 31 | join: { 32 | from: 'users.id', 33 | to: 'pets.owner_id' 34 | } 35 | } 36 | }) 37 | } 38 | 39 | export default User; -------------------------------------------------------------------------------- /src/database/models/index.ts: -------------------------------------------------------------------------------- 1 | import User from './User'; 2 | import Pet from './Pet'; 3 | 4 | export { 5 | User, 6 | Pet 7 | } -------------------------------------------------------------------------------- /src/database/seeds/baseSeed.js: -------------------------------------------------------------------------------- 1 | exports.seed = function (knex) { 2 | // Deletes ALL existing entries 3 | return knex("users") 4 | .del() 5 | .then(function () { 6 | // Inserts seed entries 7 | return knex("users").insert([ 8 | { full_name: "luis", country_code: 58 }, 9 | { full_name: "jose", country_code: 59 }, 10 | { full_name: "raul", country_code: 39 }, 11 | ]); 12 | }) 13 | .then(() => { 14 | return knex("pets") 15 | .del() 16 | .then(function () { 17 | // Inserts seed entries 18 | return knex("pets").insert([ 19 | { name: "spot", owner_id: 1, specie: "MAMMALS" }, 20 | { name: "chief", owner_id: 2, specie: "MAMMALS" }, 21 | { name: "king", owner_id: 3, specie: "MAMMALS" }, 22 | ]); 23 | }); 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import express, { Application } from "express"; 2 | import { ApolloServer, Config } from "apollo-server-express"; 3 | import schema from "./schema"; 4 | import Knex from "knex"; 5 | import { Model } from "objection"; 6 | import dbconfig from "./database/config"; 7 | const db = Knex(dbconfig["development"]); 8 | import DataLoader from "dataloader"; 9 | import { Pets, Users } from "./utils/loaders"; 10 | import Mock from "mock-knex"; 11 | 12 | Model.knex(db); 13 | 14 | 15 | const app: Application = express(); 16 | 17 | const config: Config = { 18 | schema: schema, 19 | introspection: true, //these lines are required to use the gui 20 | playground: true, // of playground 21 | tracing: true, 22 | context: { 23 | loaders: { 24 | users: new DataLoader(Users), 25 | pets: new DataLoader(Pets), 26 | }, 27 | }, 28 | }; 29 | 30 | const server: ApolloServer = new ApolloServer(config); 31 | 32 | server.applyMiddleware({ 33 | app, 34 | path: "/graphql", 35 | }); 36 | 37 | app.listen(3000, () => { 38 | // change later to 4000 39 | console.log("We are running on http://localhost:3000/graphql"); 40 | }); 41 | -------------------------------------------------------------------------------- /src/schema/graphql/schema.gql: -------------------------------------------------------------------------------- 1 | enum Species{ 2 | BIRDS, 3 | FISH, 4 | MAMMALS, 5 | REPTILES 6 | } 7 | 8 | type User { 9 | id: Int! 10 | full_name: String 11 | country_code: String 12 | created_at:String 13 | pets:[Pet] 14 | } 15 | 16 | type Pet { 17 | id: Int! 18 | name: String 19 | owner_id: Int! 20 | specie: Species 21 | created_at:String 22 | owner:User 23 | } 24 | 25 | input createUserInput{ 26 | full_name: String! 27 | country_code: String! 28 | } 29 | 30 | input createPetInput{ 31 | name: String! 32 | owner_id: Int! 33 | specie: Species! 34 | } 35 | 36 | input updateUserInput{ 37 | id:Int! 38 | full_name: String 39 | country_code: String 40 | } 41 | 42 | 43 | input updatePetInput{ 44 | id:Int! 45 | name: String! 46 | } 47 | 48 | type Query{ 49 | pets:[Pet] 50 | users:[User] 51 | user(id:Int!):User 52 | pet(id:Int!):Pet 53 | } 54 | 55 | type Mutation{ 56 | createPet(pet:createPetInput!):Pet 57 | createUser(user:createUserInput!):User 58 | deletePet(id:Int!):String 59 | deleteUser(id:Int!):String 60 | updatePet(pet:updatePetInput!):Pet 61 | updateUser(user:updateUserInput!):User 62 | } -------------------------------------------------------------------------------- /src/schema/index.ts: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema} from 'graphql-tools'; 2 | import schema from './graphql/schema.gql'; 3 | import {user,pet} from './resolvers'; 4 | 5 | const resolvers=[user,pet]; 6 | 7 | export default makeExecutableSchema({typeDefs:schema, resolvers: resolvers as any}); -------------------------------------------------------------------------------- /src/schema/resolvers/index.ts: -------------------------------------------------------------------------------- 1 | import user from './user'; 2 | import pet from './pet'; 3 | 4 | 5 | export { 6 | user, 7 | pet 8 | } -------------------------------------------------------------------------------- /src/schema/resolvers/pet.ts: -------------------------------------------------------------------------------- 1 | import { Pet, User } from "../../database/models"; 2 | import { Resolvers } from "../../__generated__/generated-types"; 3 | import { UserInputError } from "apollo-server-express"; 4 | 5 | const resolvers: Resolvers = { 6 | Query: { 7 | pet: async (parent, args, ctx) => { 8 | const pet: Pet = await Pet.query().findById(args.id); 9 | 10 | return pet; 11 | }, 12 | pets: async (parent, args, ctx) => { 13 | const pets: Pet[] = await Pet.query(); 14 | 15 | return pets; 16 | }, 17 | }, 18 | Pet: { 19 | owner: async (parent, args, ctx) => { 20 | const { 21 | loaders: { users }, 22 | } = ctx; 23 | return users.load(parent.owner_id); 24 | }, 25 | }, 26 | Mutation: { 27 | createPet: async (parent, args, ctx) => { 28 | let pet: Pet; 29 | try { 30 | pet = await Pet.query().insert({ ...args.pet }); 31 | } catch (error) { 32 | console.log(error); 33 | throw new UserInputError("Bad pet input fields required", { 34 | invalidArgs: Object.keys(args), 35 | }); 36 | } 37 | return pet; 38 | }, 39 | updatePet: async (parent, { pet: { id, ...data } }, ctx) => { 40 | const pet: Pet = await Pet.query().patchAndFetchById(id, data); 41 | 42 | return pet; 43 | }, 44 | deletePet: async (parent, args, ctx) => { 45 | const pet = await Pet.query().deleteById(args.id); 46 | return "Successfully deleted"; 47 | }, 48 | }, 49 | }; 50 | 51 | export default resolvers; 52 | -------------------------------------------------------------------------------- /src/schema/resolvers/user.ts: -------------------------------------------------------------------------------- 1 | import { Resolvers } from "../../__generated__/generated-types"; 2 | import { User, Pet } from "../../database/models"; 3 | import { UserInputError } from "apollo-server-express"; 4 | 5 | const resolvers: Resolvers = { 6 | Query: { 7 | users: async (parent, args, ctx) => { 8 | const users: User[] = await User.query(); 9 | return users; 10 | }, 11 | user: async (parent, args, ctx) => { 12 | const user: User = await User.query().findById(args.id); 13 | 14 | return user; 15 | }, 16 | }, 17 | User: { 18 | pets: async (parent, args, ctx) => { 19 | const { 20 | loaders: { pets }, 21 | } = ctx; 22 | return pets.load(parent.id); 23 | }, 24 | }, 25 | Mutation: { 26 | createUser: async (parent, args, ctx) => { 27 | let user: User; 28 | try { 29 | user = await User.query().insert({ ...args.user }); 30 | } catch (error) { 31 | console.log(error, Object.keys(args)); 32 | throw new UserInputError("Bad data", { 33 | invalidArgs: Object.keys(args), 34 | }); 35 | } 36 | return user; 37 | }, 38 | updateUser: async (parent, { user: { id, ...data } }, ctx) => { 39 | let user: User = await User.query().patchAndFetchById(id, data); 40 | 41 | return user; 42 | }, 43 | deleteUser: async (parent, args, ctx) => { 44 | const deleted = await User.query().deleteById(args.id); 45 | return "Succesfull deleted"; 46 | }, 47 | }, 48 | }; 49 | 50 | export default resolvers; 51 | -------------------------------------------------------------------------------- /src/utils/loaders.ts: -------------------------------------------------------------------------------- 1 | import { BatchLoadFn } from "dataloader"; 2 | import { Pet, User } from "../database/models"; 3 | 4 | export const Pets: BatchLoadFn> = async (ids) => { 5 | const pets = await Pet.query(); 6 | 7 | return ids.map((i) => pets.filter((item) => item.owner_id === i)); 8 | }; 9 | 10 | export const Users: BatchLoadFn> = async (ids) => { 11 | const users = await User.query(); 12 | 13 | return ids.map((id) => users.filter((i) => i.id === id)); 14 | }; 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "dist", /* Redirect output structure to the directory. */ 18 | "rootDir": "src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | }, 69 | "files": ["./index.d.ts"] 70 | } 71 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const {CheckerPlugin} = require('awesome-typescript-loader'); 3 | var nodeExternals = require('webpack-node-externals'); 4 | //nunca olvides de colocar target node en webpack 5 | //node externals pior el error de aws-sdk 6 | module.exports ={ 7 | entry: './src/index.ts', 8 | target:'node', 9 | externals: [nodeExternals(),{ knex: 'commonjs knex' }], 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: 'bundle.js' 13 | }, 14 | resolve: { 15 | extensions: [ ".mjs",'.js', '.ts','.(graphql|gql)'], 16 | modules: [ 17 | 18 | 'src', 19 | ] 20 | }, 21 | module:{ 22 | rules:[ 23 | { 24 | test: /\.(graphql|gql)$/, 25 | exclude: /node_modules/, 26 | loader: 'graphql-tag/loader' 27 | }, 28 | { 29 | test: /\.ts$/, 30 | exclude: /node_modules/, 31 | loaders: 'awesome-typescript-loader' 32 | } 33 | ] 34 | }, 35 | plugins:[ 36 | new CheckerPlugin(), 37 | ] 38 | 39 | }; 40 | -------------------------------------------------------------------------------- /yarn-error.log: -------------------------------------------------------------------------------- 1 | Arguments: 2 | C:\Program Files\nodejs\node.exe C:\Program Files (x86)\Yarn\bin\yarn.js 3 | 4 | PATH: 5 | C:\Program Files\Git\mingw64\bin;C:\Program Files\Git\usr\bin;C:\Users\USUARIO\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\WINDOWS\System32\WindowsPowerShell\v1.0;C:\WINDOWS\System32\OpenSSH;C:\Program Files\nodejs;C:\Program Files\Git\cmd;C:\Program Files (x86)\Yarn\bin;C:\Users\USUARIO\AppData\Local\Microsoft\WindowsApps;C:\Users\USUARIO\AppData\Local\Programs\Microsoft VS Code\bin;C:\Users\USUARIO\AppData\Roaming\npm;C:\Users\USUARIO\AppData\Local\Yarn\bin 6 | 7 | Yarn version: 8 | 1.22.5 9 | 10 | Node version: 11 | 12.19.0 12 | 13 | Platform: 14 | win32 x64 15 | 16 | Trace: 17 | SyntaxError: C:\Users\USUARIO\Documents\Wonder\For_Post\Graphql-typescript-postgresql\package.json: Unexpected token < in JSON at position 812 18 | at JSON.parse () 19 | at C:\Program Files (x86)\Yarn\lib\cli.js:1625:59 20 | at Generator.next () 21 | at step (C:\Program Files (x86)\Yarn\lib\cli.js:310:30) 22 | at C:\Program Files (x86)\Yarn\lib\cli.js:321:13 23 | 24 | npm manifest: 25 | { 26 | "name": "Graphql-typescript-postgresql", 27 | "version": "1.0.0", 28 | "main": "index.js", 29 | "license": "MIT", 30 | "scripts": { 31 | "dev": "concurrently \" nodemon ./dist/bundle.js \" \" webpack --watch --config webpack.config.js\" ", 32 | "build": "webpack", 33 | "test:integration": "jest /src -c jest.config.js --forceExit ", 34 | "start": "node /dist/bundle.js", 35 | "migrate:up": "knex --knexfile ./src/database/knexfile.ts migrate:latest ", 36 | "test:migrate:up": "knex --knexfile ./src/database/knexfile.ts migrate:latest --env testing", 37 | "test:migrate:down": "knex --knexfile ./src/database/knexfile.ts migrate:down --env testing", 38 | "generate:types": "graphql-codegen --config codegen.yml", 39 | "test:seed-db": "knex --knexfile ./src/database/knexfile.ts seed:run --env testing" 40 | }, 41 | "dependencies": { 42 | <<<<<<< HEAD 43 | "apollo-server-express": "^2.16.1", 44 | "apollo-server-testing": "^2.16.1", 45 | ======= 46 | "apollo-server-express": "^2.14.2", 47 | >>>>>>> 39c773ee1b2f4657384a929c8a505e3b0d9fcb3a 48 | "body-parser": "^1.19.0", 49 | "dataloader": "^2.0.0", 50 | "express": "^4.17.1", 51 | "graphql": "^15.0.0", 52 | "graphql-tools": "^6.0.0", 53 | "knex": "^0.21.1", 54 | "objection": "^2.1.3", 55 | "pg": "^8.2.0" 56 | }, 57 | "devDependencies": { 58 | "@graphql-codegen/cli": "1.14.0", 59 | "@graphql-codegen/typescript": "1.14.0", 60 | "@graphql-codegen/typescript-operations": "^1.14.0", 61 | "@graphql-codegen/typescript-resolvers": "1.14.0", 62 | "@jagi/jest-transform-graphql": "^1.0.2", 63 | "@types/express": "^4.17.6", 64 | "@types/graphql": "^14.5.0", 65 | "@types/jest": "^26.0.3", 66 | "@types/mock-knex": "^0.4.3", 67 | "@types/node": "^14.0.1", 68 | "awesome-typescript-loader": "^5.2.1", 69 | "concurrently": "^5.2.0", 70 | "graphql-tag": "^2.10.3", 71 | "jest": "^26.2.1", 72 | "knex-cleaner": "^1.3.1", 73 | "nodemon": "^2.0.4", 74 | "ts-jest": "^26.1.1", 75 | "ts-node": "^8.10.1", 76 | "typescript": "^3.9.2", 77 | "webpack": "^4.43.0", 78 | "webpack-cli": "^3.3.11", 79 | "webpack-node-externals": "^1.7.2" 80 | } 81 | } 82 | 83 | yarn manifest: 84 | No manifest 85 | 86 | Lockfile: 87 | No lockfile 88 | --------------------------------------------------------------------------------