Skip to content

Commit

Permalink
Update Chapter 3 and 4 to have actual mutations (more motivation)
Browse files Browse the repository at this point in the history
  • Loading branch information
Lauri Svan committed Dec 11, 2020
1 parent 94bf5b8 commit dc6bed8
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 46 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ This workshop will use the following tools:
- [Postgres (any version)](https://www.postgresql.org/download/)
- [Postico](https://eggerapps.at/postico/) or another Postgres database client
- [JQ for CLI JSON manipulation](https://stedolan.github.io/jq/download/) for JSON manipulation - used in a few scripts
- [Sponge from moreutils for CLI JSON manipulation](https://stedolan.github.io/jq/download/) for JSON manipulation - used in a few scripts
- [Sponge from moreutils for CLI JSON manipulation](https://joeyh.name/code/moreutils/) for JSON manipulation - used in a few scripts

Quick'n'dirty setup on OS/X / Homebrew:
```sh
Expand Down
7 changes: 7 additions & 0 deletions docs/2_Bootstrap.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,18 @@ npm init --yes
# the sake of simplicity
npm install --save-dev typescript ts-node

# CLI utility that we will use to copy the GraphQL schemas to build directory
npm install --save-dev cpy-cli

# Install Apollo Server and GraphQL module
npm install --save apollo-server graphql

# Sequelize dependencies
npm install --save sequelize@5 sequelize-typescript @types/node @types/bluebird reflect-metadata

# Utility library to generate UUIDs server-side
npm install --save uuid @types/uuid

# Postgres dependencies
npm install --save pg

Expand Down Expand Up @@ -69,6 +75,7 @@ jq '.main = "build/index.js"' package.json | sponge package.json
jq -r '.scripts.start = "node ."' package.json | sponge package.json
jq -r '.scripts.watch = "nodemon --watch build --watch schema.graphql"' package.json | sponge package.json
jq -r '.scripts.dev = "nodemon --watch build --watch schema.graphql --exec 'ts-node' ./src/index.ts"' package.json | sponge package.json
jq -r '.scripts.copy_schemas = "cpy **/*schema.graphql ../build/ --parents --cwd=src"' package.json | sponge package.json
jq -r '.scripts.build = "tsc -b --incremental"' package.json | sponge package.json
jq -r '.scripts.build_watch = "tsc -b --watch"' package.json | sponge package.json
```
Expand Down
19 changes: 13 additions & 6 deletions docs/3_Simple_Backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ const typeDefs = gql\`\${schema}\`
const resolvers = {
Query: {
hello: () => 'Hello, world!'
},
Mutation: {
echo: (parent, params, context, info) => `You typed ${ params.input }`
}
}
Expand All @@ -48,19 +52,22 @@ type Query {
hello: String
}
# All mutations
type Mutation {
echo (input: String!): String
}
EOF
```

## Run the Server

The stack is now ready for running. Start the each on different shells:
The stack is now ready for running. If you added the NPM scripts in the previous
chapter, you should be able to start 'dev' script:

```sh
# Rebuild on file changes (*.ts)
npm run build_watch

# Restart server on schema or build changes in a different shell
npm run watch
# Restarts and builds from scratch on every file change:
npm run dev

# Now the backend is responding on port 4000; also serves GraphQL Playground
open http://localhost:4000
Expand Down
151 changes: 113 additions & 38 deletions docs/4_Refined_Backend.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,27 @@ You can browse this in schema browsers
TODO Add definitions for starts and ends when we add custom data types
"""
type Assignment {
# A single-line comment (note that ! denotes non-nullable value)
id: ID!
# A single-line comment
name: String!
"""
Multi-line comments are supported here, as well
"""
description: String
starts: DateTime!
ends: DateTime!
}

"""
These are the actual querys that our Assignments module provides
In GraphQL, inputs are special types to encapsulate complex inputs
"""
type Query {
"""
Queries the assignments
Note how exclamation mark specifies that the return values are not nullable;
in GraphQL the default policy is that values are nullable, but this is a
matter of taste.
TODO Add query parameters to query the assignments with
"""
assignments: [Assignment!]!
input AssignmentInput {
name: String!
description: String
starts: DateTime!
ends: DateTime!
}

```

### Bootstrap GraphQL Codegen
Expand Down Expand Up @@ -123,41 +122,30 @@ import fs from 'fs'
import path from 'path'
import { createModule, gql } from 'graphql-modules'

// These are types injected from the generated schema types
import { IResolvers, IAssignment } from '../../interfaces/schema-typings'

import AssignmentProvider from './provider'

const data = fs.readFileSync(path.join(__dirname, 'schema.graphql'))
const typeDefs = gql(data.toString())

// We will supply Assignment specific resolvers here later
const resolvers: IResolvers = {
Query: {
assignments: async (parent, args, context, info): Promise<IAssignment[]> => {
const provider = context.injector.get(AssignmentProvider)

return provider.find()
}
}
}

export const AssignmentModule = createModule({
export const Assignment = createModule({
id: 'assignments',
dirname: __dirname,
typeDefs: typeDefs,
resolvers,
providers: [AssignmentProvider]
})

export default AssignmentModule
export default Assignment

```

Note that `IAssignment` and `IResolvers` are generated GraphQL type definitions.

### Refine Application Bootstrap Code

Append a root level GraphQL module definition directly into `src/index.ts`:
Append our first assignment module directly into `src/index.ts`:

```typescript
import { ApolloServer } from 'apollo-server'
Expand Down Expand Up @@ -407,6 +395,23 @@ Define the provider into `src/modules/assignment/provider.ts`:
import { Injectable } from 'graphql-modules'
import Assignment from '../../models/Assignment'

/**
* This is a sample provider (just a logic wrapper) that is used as glue
* between the database models and queries.
*
* Note that the `Injectable` decorator makes this provider injectable.
* DI in graphql-modules is very much like Inversify, but it has additional
* helpers for different life-cycles (singleton, session, request).
*
* We make the injectable global, as we expect to share it across the modules
*/
@Injectable({ global: true })

import { IAssignment } from '../../interfaces/schema-typings'
import { Injectable } from 'graphql-modules'
import { v4 as uuidv4 } from 'uuid'
import Assignment from '../../models/Assignment'

/**
* This is a sample provider (just a logic wrapper) that is used as glue
* between the database models and queries.
Expand All @@ -415,29 +420,41 @@ import Assignment from '../../models/Assignment'
* DI in graphql-modules is very much like Inversify, but it has additional
* helpers for different life-cycles (singleton, session, request).
*/
@Injectable()
@Injectable({ global: true })
export class AssignmentProvider {
async find (): Promise<Assignment[]> {
return await Assignment.findAll()
}

async create (input: IAssignment): Promise<Assignment> {
return await Assignment.create({
...input,
id: uuidv4()
})
}
}

export default AssignmentProvider

```

Then use the module in as part of Assignment module (`src/modules/assignment/index.ts`):
Finally, let us define the related queries. In this workshop we define all
queries and mutations in their own module `src/modules/operations`. This is
contrary to best practice to keep the operations close to their related types.
We simply do this as a technical workaround to create schemas that work both
for graphql-codegen and graphql-modules that handle GraphQL type extensions
differently since graphql-modules 1.0 release.
See more [here](https://github.com/Urigo/graphql-modules/issues/1300).

```typescript
Module definition (`src/modules/operations/index.ts`)
```typescript
import fs from 'fs'
import path from 'path'
import { createModule, gql } from 'graphql-modules'
import AssignmentProvider from '../assignment/provider'

// These are types injected from the generated schema types
import { IResolvers, IAssignment } from '../../interfaces/schema-typings'

import AssignmentProvider from './provider'

const data = fs.readFileSync(path.join(__dirname, 'schema.graphql'))
const typeDefs = gql(data.toString())

Expand All @@ -448,18 +465,76 @@ const resolvers: IResolvers = {

return provider.find()
}
},
Mutation: {
createAssignment: async (parent, { input }, context, info): Promise<IAssignment> => {
const provider = context.injector.get(AssignmentProvider)

return provider.create(input)
}
}
}

export const Assignment = createModule({
id: 'assignments',
export const OperationsModule = createModule({
id: 'operations',
dirname: __dirname,
typeDefs: typeDefs,
resolvers,
providers: [AssignmentProvider]
resolvers
})

export default Assignment
export default OperationsModule

```

Module schema definition (`src/modules/operations/schema.graphql`):

```graphql
type Query {
assignments: [Assignment!]!
}

type Mutation {
createAssignment(input: AssignmentInput): Assignment!
}
```

Finally add the new module into your application bootstrap (`src/index.ts`).
In the end of the chapter, it should look as follows:

```typescript
import { ApolloServer } from 'apollo-server'
import { createApplication } from 'graphql-modules'
import AssignmentModule from './modules/assignment'
import OperationsModule from './modules/operations'

import Database from './models/Database'

async function start (): Promise<void> {
// Initialize GraphQL modules
const application = createApplication({
modules: [
AssignmentModule,
OperationsModule
]
})

// This is the aggregated schema
const schema = application.createSchemaForApollo()

// Initialize database. Sequelize creates tables on bootstrapping
const db = Database.instance
await db.init()

// Start the server
const server = new ApolloServer({
schema
})
const { url } = await server.listen()
console.log(`🚀 Server ready at ${url}`)
}

// eslint-disable-next-line @typescript-eslint/no-floating-promises
start()

```

Expand Down
56 changes: 55 additions & 1 deletion docs/6_Custom_DataTypes_Data_Loaders.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,9 +258,63 @@ npm run generate_types

... and finally you can use custom types accross the stack!

### Data Loaders (Solving the N+1 Problem & Caching)
### More Modules

Until now we have only worked with Assignments module but have not related it
to Persons and Customers. Now we can finally show the true power of GraphQL,
e.g. give clients the power to retrieve what they want to retrieve, and when.

First we need to create the stub modules to Person and Customer. The current
API is so simple that you can just copy the `assignment` module as a template,
and substitute `assignment` with `customer` and `Assignment` with `Customer`

```sh
cp -R src/modules/assignment src/modules/customer
sed -i '' 's/assignment/customer/g' src/modules/customer/*
sed -i '' 's/Assignment/Customer/g' src/modules/customer/*

cp -R src/modules/assignment src/modules/person
sed -i '' 's/assignment/person/' src/modules/person/*
sed -i '' 's/Assignment/Person/' src/modules/person/*
```

You may want to cleanup the schema definitions for obsolete values (Customers
and Persons do not have `starts`, `ends` or `description`)

Then add the new modules into `src/index.ts`:

```typescript
import { ApolloServer } from 'apollo-server'
import { createApplication } from 'graphql-modules'
import AssignmentModule from './modules/assignment'
import CommonModule from './modules/common'
import CustomerModule from './modules/customer'
import OperationsModule from './modules/operations'
import PersonModule from './modules/person'

import Database from './models/Database'

async function start (): Promise<void> {
// Initialize GraphQL modules
const application = createApplication({
modules: [
AssignmentModule,
CustomerModule,
CommonModule,
OperationsModule,
PersonModule
]
})

...

```
Now generate types and find the new APIs in your API client and watch your API expand!
```
npm run generate_types
```
## References
Expand Down

0 comments on commit dc6bed8

Please sign in to comment.