In this post we are picking up right where we left off in Part I. If you haven't read the first post, you should do that first. As a reminder, we have already built our boilerplate frontend and backend web servers, and dockerized them. In this post we will configure our Frontend server to hook it up with our backend server using Apollo and we will add Ant Design for a nice site layout.
Where's the code?
We have the final code posted over on Github.
#Table of Contents:
Part I- Set up the Repo and Configure the Backend
-
Create the base repo
-
Create the Next.js frontend
-
Create the boilerplate Prisma 2 backend
-
Dockerize our web app
a. Create a docker-compose file
b. Add Dockerfiles for each container
-
Configure the backend
a. Switch database from SQLite to MySQL
b. Remove unused backend boilerplate code
c. Update backend queries in Prisma Nexus
-
Verify our Docker-Compose cluster works
Part II- Configure the Frontend (this post)
- Add GraphQL fragments, queries and resolvers
- Add GraphQL-Codegen for generating React Components
- Add Apollo and create HOC for Next.js
- Add React-Apollo to project root
- Install Antd for a beautiful site layout
- Create the Main Layout
- Create a Users Layout Component
- Create a Signup User Component
- Create a Feed Layout Component
- Create a New Draft Component
- Create a Publish Draft Button Component
- Create a Delete Post Button Component
Just like before- be sure to check out the end for videos that walk through this whole process.
Part II- Configure the Frontend
1. Add our GraphQL Fragments, Queries and Resolvers
The goal of the first half of this post is to use code generation to create the most difficult React components for us. In order to do that, we need to first specify all the queries and mutations that our frontend will be using. This will include information about which input parameters are needed and which fields we wish to get back from our requests. We will create all of these using graphql files.
Add fragments
First, in order to encourage code reusibility, let's create fragments for our Post and User objects:
/frontend/graphql/fragments/post.gql
fragment PostFragment on Post {
id
published
title
content
published
}
/frontend/graphql/fragments/user.gql
fragment UserFragment on User {
id
name
email
}
Add queries
We can use these fragments in our queries and mutations. Let's start by creating our queries first:
/frontend/graphql/queries/feed.gql
#import from '../fragments/post.gql'
query feedQuery($published: Boolean!) {
feed(published: $published) {
...PostFragment
}
}
/frontend/graphql/queries/post.gql
#import from '../fragments/post.gql'
query postQuery($id: ID!) {
post(where: { id: $id }) {
...PostFragment
}
}
/frontend/graphql/queries/users.gql
#import from '../fragments/user.gql'
query usersQuery {
users {
...UserFragment
}
}
Add mutations
Now let's create our mutations:
/frontend/graphql/mutations/createDraft.gql
#import from '../fragments/post.gql'
mutation createDraftMutation(
$title: String!
$content: String!
$authorEmail: String!
) {
createDraft(title: $title, content: $content, authorEmail: $authorEmail) {
...PostFragment
}
}
/frontend/graphql/mutations/deleteOnePost.gql
#import from '../fragments/post.gql'
mutation deleteOnePost($id: ID!) {
deleteOnePost(where: { id: $id }) {
...PostFragment
}
}
/frontend/graphql/mutations/publish.gql
#import from '../fragments/post.gql'
mutation publishMutation($id: ID!) {
publish(id: $id) {
...PostFragment
}
}
/frontend/graphql/mutations/signupUser.gql
#import from '../fragments/user.gql'
mutation signupUserMutation($name: String!, $email: String!) {
signupUser(data: { name: $name, email: $email }) {
...UserFragment
}
}
2. Add Graphql-Codegen to the frontend
Graphql-Codegen will take in our graphQL queries, mutations, and fragments and query against our backend server to create a generated file that contains React Components for all our possible Apollo operations that we could do with our backend server.
First install the codegen tools:
npm install --save-dev @graphql-codegen/cli @graphql-codegen/typescript @graphql-codegen/typescript-operations @graphql-codegen/typescript-react-apollo graphql
Next, we have to add the codegen file which contains configuration for how the code generation should behave:
frontend/codegen.yml
overwrite: true
schema: 'http://backend:4000/'
documents: graphql/**/*.gql
generates:
generated/apollo-components.tsx:
config:
withHOC: false
withComponent: true
plugins:
- 'typescript'
- 'typescript-operations'
- 'typescript-react-apollo'
Finally, add a npm script to the package.json file in our root:
/package.json
"generate": "docker exec -it frontend npm run generate",
and this npm script to your frontend/package.json
:
"generate": "gql-gen --config codegen.yml"
Now run npm run generate
from the root project. We can see that calling the generate script from the root will execute an npm run script call within our frontend container which will call the gql-gen tool.
Since we created a volume between our frontend app folder and the frontend folder in our computer, any files that are generated in the docker container will make their way to the host. It is for this reason that you should see that there is now a new file frontend/generated/apollo-components.tsx
that has all the typescript types, graphql documents, and react components. It is almost 300 lines of code so it is so nice that we don't have go create this. Be sure to run the generate again if you ever add new files to the graphql folder on the frontend so that this file will regenerate for you.
3. Install Apollo and create HOC for Next.js
Within the frontend directory install the following libraries:
npm install --save apollo-boost isomorphic-unfetch react-apollo
Create the frontend/utils/init-apollo.js
and frontend/utils/with-apollo-client.js
files.
frontend/utils/init-apollo.js
import { ApolloClient, InMemoryCache, HttpLink } from 'apollo-boost'
import fetch from 'isomorphic-unfetch'
let apolloClient = null
function create(initialState) {
// Check out https://github.com/zeit/next.js/pull/4611 if you want to use the AWSAppSyncClient
const isBrowser = typeof window !== 'undefined'
return new ApolloClient({
connectToDevTools: isBrowser,
ssrMode: !isBrowser, // Disables forceFetch on the server (so queries are only run once)
link: new HttpLink({
uri: isBrowser ? 'http://localhost:4000' : 'http://backend:4000', // Server URL (must be absolute)
credentials: 'same-origin', // Additional fetch() options like `credentials` or `headers`
// Use fetch() polyfill on the server
fetch: !isBrowser && fetch,
}),
cache: new InMemoryCache().restore(initialState || {}),
})
}
export default function initApollo(initialState) {
// Make sure to create a new client for every server-side request so that data
// isn't shared between connections (which would be bad)
if (typeof window === 'undefined') {
return create(initialState)
}
// Reuse client on the client-side
if (!apolloClient) {
apolloClient = create(initialState)
}
return apolloClient
}
frontend/utils/with-apollo-client.js
import React from 'react'
import initApollo from './init-apollo'
import Head from 'next/head'
import { getDataFromTree } from 'react-apollo'
export default App => {
return class Apollo extends React.Component {
static displayName = 'withApollo(App)'
static async getInitialProps(ctx) {
const { Component, router } = ctx
let appProps = {}
if (App.getInitialProps) {
appProps = await App.getInitialProps(ctx)
}
// Run all GraphQL queries in the component tree
// and extract the resulting data
const apollo = initApollo()
if (typeof window === 'undefined') {
try {
// Run all GraphQL queries
await getDataFromTree(
<App
{...appProps}
Component={Component}
router={router}
apolloClient={apollo}
/>
)
} catch (error) {
// Prevent Apollo Client GraphQL errors from crashing SSR.
// Handle them in components via the data.error prop:
// https://www.apollographql.com/docs/react/api/react-apollo.html#graphql-query-data-error
console.error('Error while running `getDataFromTree`', error)
}
// getDataFromTree does not call componentWillUnmount
// head side effect therefore need to be cleared manually
Head.rewind()
}
// Extract query data from the Apollo store
const apolloState = apollo.cache.extract()
return {
...appProps,
apolloState,
}
}
constructor(props) {
super(props)
this.apolloClient = initApollo(props.apolloState)
}
render() {
return <App {...this.props} apolloClient={this.apolloClient} />
}
}
}
These two files are boilerplate code that are taken from Next.js samples of working with Apollo and GraphQL- the first file creates a function that will connect to our backend server in two different ways depending on whether its during the pre-rendering step that occurs on the Next.js server or if it is in the user's browser.
The only change made from the original boilerplate code is that in the create HttpLink, we connect to http://localhost:4000
on the server if we are the browser but if we are in the frontend docker container we actually will connect to http://backend:4000
. This is because docker-compose handles networking for us so we don't have to know what the actual IP address of the backend container is within the docker network- we can simply refer to it by a DNS name which is our container name, and docker takes care of the networking for us. Neat!
uri: isBrowser ? 'http://localhost:4000' : 'http://backend:4000',
4. Add React-Apollo to the root of the Next project.
Now that we've created the withApolloClient HOC, we can use it in the _app.tsx file. This is a special file in the pages folder which will run on every page of the Next.js site. This is exactly what we need to ensure that we have Apollo access everywhere.
Create a new file frontend/pages/_app.tsx
import App, { Container } from 'next/app'
import React from 'react'
import withApolloClient from '../utils/with-apollo-client'
import { ApolloProvider } from 'react-apollo'
class MyApp extends App {
render() {
// @ts-ignore
const { Component, pageProps, apolloClient } = this.props
return (
<Container>
<ApolloProvider client={apolloClient}>
<Component {...pageProps} />
</ApolloProvider>
</Container>
)
}
}
export default withApolloClient(MyApp)
5. Install Antd for a beautiful site layout
Ant Design is a popular React UI framework that we will be using in this project. It is like Bootstrap but I think that it fits even better into the React landscape because you don't have to install jQuery for modal popups and it just generally has a look that is super clean and doesn't look like every other site out there. First we need to install it in our frontend folder:
npm install --save antd
Then we need to add the css styling to the _app.tsx
file. Just add it to the bottom of the depedencies import list:
import 'antd/dist/antd.css'
In Part 3 of this blog post series we will show how to use these Ant Design components to build out all of our React Components so be sure to check back for our final installment.
Video Series for Part II:
Create Queries, Mutations and Fragments for Next.js
Add GraphQL Code Generator
Add Apollo to Next.js
Add Apollo Provider to Next.js
Add Ant Design to Next.js