Skip to content

Commit

Permalink
refactor(examples/with-typescript-graphql): use codegen `TypedDocumen…
Browse files Browse the repository at this point in the history
…tNode` and GraphQL Yoga for better DX and smaller bundle size (#36240)

Improve the Next.js with TypeScript + GraphQL example:

- [x] use GraphQL Code Generator instead of `graphql-let`: more widespread tool and smaller bundle size (types only generation vs code generation)
- [x] use GraphQL Yoga instead of Apollo Server Micro: for lighter bundle size as [stated here](#36155)
- [x] introduces GraphQL Code Generator on the API side for Resolvers typing

Co-authored-by: JJ Kasper <22380829+ijjk@users.noreply.github.com>
  • Loading branch information
charlypoly and ijjk committed May 23, 2022
1 parent 874957e commit 565a2fa
Show file tree
Hide file tree
Showing 20 changed files with 126 additions and 140 deletions.
4 changes: 0 additions & 4 deletions examples/with-typescript-graphql/.babelrc

This file was deleted.

3 changes: 3 additions & 0 deletions examples/with-typescript-graphql/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,6 @@ yarn-error.log*
*.graphql.d.ts
*.graphqls.d.ts
.cache

lib/resolvers-types.ts
lib/graphql-operations.ts
6 changes: 0 additions & 6 deletions examples/with-typescript-graphql/.graphql-let.yml

This file was deleted.

15 changes: 8 additions & 7 deletions examples/with-typescript-graphql/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@

One of the strengths of GraphQL is [enforcing data types on runtime](https://graphql.github.io/graphql-spec/June2018/#sec-Value-Completion). Further, TypeScript and [GraphQL Code Generator](https://graphql-code-generator.com/) (graphql-codegen) make it safer by typing data statically, so you can write truly type-protected code with rich IDE assists.

This template extends [Apollo Server and Client Example](https://github.com/vercel/next.js/tree/canary/examples/api-routes-apollo-server-and-client#readme) by rewriting in TypeScript and integrating [graphql-let](https://github.com/piglovesyou/graphql-let#readme), which runs [TypeScript React Apollo](https://graphql-code-generator.com/docs/plugins/typescript-react-apollo) in [graphql-codegen](https://github.com/dotansimha/graphql-code-generator#readme) under the hood. It enhances the typed GraphQL use as below:
This template gives you the best start to use GraphQL with fully typed queries (client-side) and resolvers (server-side), all this with minimum bundle size 📦

```tsx
import { useNewsQuery } from './news.graphql'
import { useQuery } from '@apollo/client'
import { ViewerDocument } from 'lib/graphql-operations'

const News = () => {
// Typed already️⚡️
const { data: { news } } = useNewsQuery()
// Typed already️⚡️
const {
data: { viewer },
} = useQuery(ViewerDocument)

return <div>{news.map(...)}</div>
return <div>{viewer.name}</div>
}
```

By default `**/*.graphqls` is recognized as GraphQL schema and `**/*.graphql` as GraphQL documents. If you prefer the other extensions, make sure the settings of the webpack loader in `next.config.js` and `.graphql-let.yml` are consistent.

## Deploy your own

Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) or preview live with [StackBlitz](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-typescript-graphql)
Expand Down
14 changes: 14 additions & 0 deletions examples/with-typescript-graphql/codegen.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
schema:
- 'lib/schema.ts':
noRequire: true
documents: ./lib/documents/*.graphql
generates:
./lib/graphql-operations.ts:
plugins:
- typescript
- typescript-operations
- typed-document-node
./lib/resolvers-types.ts:
plugins:
- typescript
- typescript-resolvers
6 changes: 0 additions & 6 deletions examples/with-typescript-graphql/graphql.d.ts

This file was deleted.

9 changes: 8 additions & 1 deletion examples/with-typescript-graphql/lib/apollo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
InMemoryCache,
NormalizedCacheObject,
} from '@apollo/client'
import resolvers from './resolvers'
import typeDefs from './schema'

let apolloClient: ApolloClient<NormalizedCacheObject> | undefined

Expand All @@ -16,7 +18,12 @@ export type ResolverContext = {
function createIsomorphLink(context: ResolverContext = {}) {
if (typeof window === 'undefined') {
const { SchemaLink } = require('@apollo/client/link/schema')
const { schema } = require('./schema')
const { makeExecutableSchema } = require('@graphql-tools/schema')

const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
return new SchemaLink({ schema, context })
} else {
const { HttpLink } = require('@apollo/client')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mutation UpdateName($name: String!) {
updateName(name: $name) {
id
name
status
}
}
11 changes: 11 additions & 0 deletions examples/with-typescript-graphql/lib/documents/viewer.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
query Viewer {
viewer {
...Partial
status
}
}

fragment Partial on User {
id
name
}
4 changes: 0 additions & 4 deletions examples/with-typescript-graphql/lib/partial.graphql

This file was deleted.

24 changes: 12 additions & 12 deletions examples/with-typescript-graphql/lib/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import { QueryResolvers, MutationResolvers } from '@graphql-types@'
import { ResolverContext } from './apollo'
import { Resolvers } from './resolvers-types'

const userProfile = {
id: String(1),
name: 'John Smith',
status: 'cached',
}

const Query: Required<QueryResolvers<ResolverContext>> = {
viewer(_parent, _args, _context, _info) {
return userProfile
const resolvers: Resolvers = {
Query: {
viewer(_parent, _args, _context, _info) {
return userProfile
},
},
}

const Mutation: Required<MutationResolvers<ResolverContext>> = {
updateName(_parent, _args, _context, _info) {
userProfile.name = _args.name
return userProfile
Mutation: {
updateName(_parent, _args, _context, _info) {
userProfile.name = _args.name
return userProfile
},
},
}

export default { Query, Mutation }
export default resolvers
27 changes: 15 additions & 12 deletions examples/with-typescript-graphql/lib/schema.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { join } from 'path'
import { makeExecutableSchema } from '@graphql-tools/schema'
import { loadFilesSync } from '@graphql-tools/load-files'
import { mergeTypeDefs } from '@graphql-tools/merge'
import graphQLLetConfig from '../.graphql-let.yml'
import resolvers from './resolvers'
const typeDefs = /* GraphQL */ `
type User {
id: ID!
name: String!
status: String!
}
const loadedFiles = loadFilesSync(join(process.cwd(), graphQLLetConfig.schema))
const typeDefs = mergeTypeDefs(loadedFiles)
type Query {
viewer: User!
}
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
type Mutation {
updateName(name: String!): User!
}
`

export default typeDefs
7 changes: 0 additions & 7 deletions examples/with-typescript-graphql/lib/type-defs.graphqls

This file was deleted.

5 changes: 0 additions & 5 deletions examples/with-typescript-graphql/lib/user.graphqls

This file was deleted.

16 changes: 0 additions & 16 deletions examples/with-typescript-graphql/lib/viewer.graphql

This file was deleted.

12 changes: 0 additions & 12 deletions examples/with-typescript-graphql/next.config.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,5 @@
module.exports = {
webpack(config, options) {
config.module.rules.push({
test: /\.graphql$/,
exclude: /node_modules/,
use: [options.defaultLoaders.babel, { loader: 'graphql-let/loader' }],
})

config.module.rules.push({
test: /\.graphqls$/,
exclude: /node_modules/,
use: ['graphql-let/schema/loader'],
})

config.module.rules.push({
test: /\.ya?ml$/,
type: 'json',
Expand Down
34 changes: 16 additions & 18 deletions examples/with-typescript-graphql/package.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,40 @@
{
"private": true,
"scripts": {
"codegen": "graphql-let",
"codegen": "graphql-codegen -r ts-node/register",
"dev": "yarn codegen && next",
"build": "yarn codegen && next build",
"test": "yarn codegen && jest",
"start": "next start"
},
"dependencies": {
"@apollo/client": "^3.1.3",
"@apollo/client": "^3.5.10",
"graphql-tag": "^2.12.6",
"@graphql-tools/load-files": "6.0.18",
"@graphql-yoga/node": "^2.2.1",
"@graphql-tools/merge": "6.0.18",
"@graphql-tools/schema": "6.0.18",
"apollo-server-micro": "^2.16.1",
"graphql": "15.3.0",
"next": "latest",
"graphql": "15.6.0",
"next": "^12.1.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
"devDependencies": {
"@graphql-codegen/cli": "^2.2.1",
"@graphql-codegen/import-types-preset": "^2.1.6",
"@graphql-codegen/plugin-helpers": "^2.2.0",
"@graphql-codegen/typescript": "^2.2.4",
"@graphql-codegen/typescript-operations": "^2.1.8",
"@graphql-codegen/typescript-react-apollo": "^3.1.6",
"@graphql-codegen/typescript-resolvers": "^2.3.2",
"@graphql-codegen/typed-document-node": "^2.2.8",
"@graphql-codegen/cli": "^2.6.2",
"@graphql-codegen/typescript": "^2.4.8",
"@graphql-codegen/typescript-operations": "^2.3.5",
"@graphql-codegen/typescript-resolvers": "^2.6.1",
"@types/jest": "^27.0.2",
"@types/mocha": "^9.0.0",
"@types/react": "^16.9.46",
"@types/react-dom": "^16.9.8",
"@types/react-test-renderer": "^17.0.1",
"@types/jest": "^27.0.2",
"@types/mocha": "^9.0.0",
"babel-jest": "27.2.5",
"graphql-let": "^0.18.6",
"graphql-tag": "2.11.0",
"jest": "^27.2.5",
"react-test-renderer": "^17.0.1",
"typescript": "^4.5.4",
"yaml-loader": "0.6.0"
"typescript": "^4.6.3",
"yaml-loader": "0.6.0",
"ts-node": "10.8.0"
}
}
20 changes: 12 additions & 8 deletions examples/with-typescript-graphql/pages/api/graphql.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { ApolloServer } from 'apollo-server-micro'
import { schema } from '../../lib/schema'
import { createServer } from '@graphql-yoga/node'
import gql from 'graphql-tag'

const apolloServer = new ApolloServer({ schema })
import resolvers from 'lib/resolvers'
import typeDefs from 'lib/schema'

export const config = {
api: {
bodyParser: false,
const server = createServer({
schema: {
typeDefs: gql(typeDefs),
resolvers,
},
}
endpoint: '/api/graphql',
// graphiql: false // uncomment to disable GraphiQL
})

export default apolloServer.createHandler({ path: '/api/graphql' })
export default server
37 changes: 20 additions & 17 deletions examples/with-typescript-graphql/pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,45 @@
import { useMutation, useQuery } from '@apollo/client'
import { UpdateNameDocument, ViewerDocument } from 'lib/graphql-operations'
import Link from 'next/link'
import { useState } from 'react'
import {
ViewerQuery,
useViewerQuery,
useUpdateNameMutation,
ViewerDocument,
} from '../lib/viewer.graphql'
import { initializeApollo } from '../lib/apollo'

const Index = () => {
const { viewer } = useViewerQuery().data!
const { data } = useQuery(ViewerDocument)
const [newName, setNewName] = useState('')
const [updateNameMutation] = useUpdateNameMutation()
const [updateNameMutation] = useMutation(UpdateNameDocument)

const onChangeName = () => {
updateNameMutation({
variables: {
name: newName,
},
//Follow apollo suggestion to update cache
//https://www.apollographql.com/docs/angular/features/cache-updates/#update
// Follow apollo suggestion to update cache
// https://www.apollographql.com/docs/angular/features/cache-updates/#update
update: (cache, mutationResult) => {
const { data } = mutationResult
if (!data) return // Cancel updating name in cache if no data is returned from mutation.
// Read the data from our cache for this query.
const { viewer } = cache.readQuery({
const result = cache.readQuery({
query: ViewerDocument,
}) as ViewerQuery
const newViewer = { ...viewer }
})
const newViewer = result ? { ...result.viewer } : null
// Add our comment from the mutation to the end.
newViewer.name = data.updateName.name
// Write our data back to the cache.
cache.writeQuery({ query: ViewerDocument, data: { viewer: newViewer } })
if (newViewer) {
newViewer.name = data.updateName.name
cache.writeQuery({
query: ViewerDocument,
data: { viewer: newViewer },
})
}
},
})
}

return (
const viewer = data?.viewer

return viewer ? (
<div>
You're signed in as {viewer.name} and you're {viewer.status}. Go to the{' '}
<Link href="/about">
Expand All @@ -52,7 +55,7 @@ const Index = () => {
<input type="button" value="change" onClick={onChangeName} />
</div>
</div>
)
) : null
}

export async function getStaticProps() {
Expand Down
Loading

0 comments on commit 565a2fa

Please sign in to comment.