├── client
└── src
│ ├── modules
│ ├── index.js
│ └── post
│ │ ├── index.js
│ │ ├── components
│ │ ├── index.js
│ │ ├── PostList.js
│ │ └── PostForm.js
│ │ ├── providers
│ │ ├── index.js
│ │ ├── PostList.js
│ │ └── AddPost.js
│ │ ├── styles
│ │ └── styles.css
│ │ └── containers
│ │ └── index.js
│ ├── config
│ └── createApolloClient.js
│ ├── index.js
│ ├── index.html
│ └── App.js
├── .gitignore
├── server
├── modules
│ └── post
│ │ ├── models
│ │ └── post.js
│ │ ├── graphqlSchema.js
│ │ └── resolvers.js
├── config
│ └── database.js
└── server.js
├── webpack.config.js
├── LICENSE
├── package.json
└── article.md
/client/src/modules/index.js:
--------------------------------------------------------------------------------
1 | export { Posts } from './post';
--------------------------------------------------------------------------------
/client/src/modules/post/index.js:
--------------------------------------------------------------------------------
1 | export { default as Posts } from './containers';
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | package-lock.json*
3 | dist/
4 | .idea
5 | yarn.lock
6 | yarn-error.log
7 |
--------------------------------------------------------------------------------
/client/src/modules/post/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as PostList } from './PostList';
2 | export { default as PostForm } from './PostForm';
--------------------------------------------------------------------------------
/client/src/modules/post/providers/index.js:
--------------------------------------------------------------------------------
1 | export { default as withPosts } from './PostList';
2 | export { default as withAddPost } from './AddPost';
--------------------------------------------------------------------------------
/client/src/config/createApolloClient.js:
--------------------------------------------------------------------------------
1 | import ApolloClient from 'apollo-boost';
2 |
3 | const client = new ApolloClient({
4 | uri: 'http://localhost:3000/graphql'
5 | });
6 |
7 | export default client;
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom';
3 |
4 | import App from './App';
5 |
6 | import 'bootstrap/dist/css/bootstrap.min.css';
7 |
8 | ReactDOM.render(, document.getElementById('root'));
--------------------------------------------------------------------------------
/server/modules/post/models/post.js:
--------------------------------------------------------------------------------
1 | const { Schema, model } = require('mongoose');
2 |
3 | const postSchema = new Schema({
4 | title: String,
5 | content: String
6 | });
7 |
8 | const Post = model('post', postSchema);
9 |
10 | module.exports = Post;
11 |
--------------------------------------------------------------------------------
/server/config/database.js:
--------------------------------------------------------------------------------
1 | const mongoose = require('mongoose');
2 | const DB_URI = 'mongodb://localhost:27017/apollo-app';
3 |
4 | mongoose.connect(DB_URI, { useNewUrlParser: true });
5 | mongoose.connection.once('open', () => console.log('Successfully connected to MongoDB'));
6 | mongoose.connection.on('error', error => console.error(error));
7 |
8 | module.exports = mongoose;
9 |
--------------------------------------------------------------------------------
/client/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | React Apollo Express Example
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/client/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 |
3 | import { ApolloProvider } from 'react-apollo';
4 |
5 | import apolloClient from './config/createApolloClient';
6 |
7 | import { Posts } from './modules';
8 |
9 | class App extends Component {
10 | render() {
11 | return (
12 |
13 |
14 |
15 | )
16 | }
17 | }
18 |
19 | export default App;
20 |
--------------------------------------------------------------------------------
/server/modules/post/graphqlSchema.js:
--------------------------------------------------------------------------------
1 | const { gql } = require('apollo-server-express');
2 |
3 | // Construct a schema using GraphQL schema language
4 | const typeDefs = gql`
5 | type Post {
6 | _id: String,
7 | title: String,
8 | content: String,
9 | },
10 | type Query {
11 | posts: [Post]
12 | },
13 | type Mutation {
14 | addPost(title: String!, content: String!): Post,
15 | }
16 | `;
17 |
18 | module.exports = typeDefs;
19 |
--------------------------------------------------------------------------------
/client/src/modules/post/providers/PostList.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { gql } from 'apollo-boost';
3 | import { Query } from 'react-apollo';
4 |
5 | export const GET_POSTS = gql`
6 | {
7 | posts {
8 | _id
9 | title
10 | content
11 | }
12 | }
13 | `;
14 |
15 | const withPosts = Component => props => {
16 | return (
17 |
18 | {({ loading, data }) => {
19 | return (
20 |
21 | );
22 | }}
23 |
24 | );
25 | };
26 |
27 | export default withPosts;
28 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const { ApolloServer } = require('apollo-server-express');
3 | const mongoose = require('./config/database');
4 |
5 | const typeDefs = require('./modules/post/graphqlSchema');
6 | const resolvers = require('./modules/post/resolvers');
7 |
8 | // Create Apollo server
9 | const server = new ApolloServer({ typeDefs, resolvers });
10 |
11 | // Create Express app
12 | const app = express();
13 |
14 | // Use Express app as middleware in Apollo Server instance
15 | server.applyMiddleware({ app });
16 |
17 | // Listen server
18 | app.listen({ port: 3000 }, () =>
19 | console.log(`🚀Server ready at http://localhost:3000${server.graphqlPath}`)
20 | );
21 |
--------------------------------------------------------------------------------
/client/src/modules/post/styles/styles.css:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: 25px;
3 | padding: 25px 15px;
4 | background-color: #f5f7f9;
5 | }
6 |
7 | .card-body {
8 | margin-bottom: 20px;
9 | }
10 |
11 | .post-card {
12 | width: 350px;
13 | margin: 7px auto;
14 | cursor: pointer;
15 | color: #880a8e;
16 | box-shadow: 1px 2px 3px rgba(136,10,142,0.3);
17 | }
18 |
19 | .post-form {
20 | padding: 25px 20px;
21 | border: 1px solid rgba(0,0,0,0.125);
22 | border-radius: 4px;
23 | box-shadow: 1px 2px 3px rgba(136,10,142,0.3);
24 | }
25 |
26 | .post-card:hover, .post-form:hover {
27 | box-shadow: 2px 3px 4px rgba(136,10,142,0.5);
28 | }
29 |
30 | .submit-button {
31 | background-color: #880a8e !important;
32 | }
33 |
--------------------------------------------------------------------------------
/server/modules/post/resolvers.js:
--------------------------------------------------------------------------------
1 | const Post = require('./models/post');
2 |
3 | // Provide resolver functions for the GraphQL schema
4 | const resolvers = {
5 | /**
6 | * A GraphQL Query for posts that uses a Post model to query MongoDB
7 | * and get all Post documents.
8 | */
9 | Query: {
10 | posts: () => Post.find({})
11 | },
12 | /**
13 | * A GraphQL Mutation that provides functionality for adding post to
14 | * the posts list and return post after successfully adding to list
15 | */
16 | Mutation: {
17 | addPost: (parent, post) => {
18 | const newPost = new Post({ title: post.title, content: post.content });
19 | return newPost.save();
20 | }
21 | }
22 | };
23 |
24 | module.exports = resolvers;
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const HtmlWebPackPlugin = require('html-webpack-plugin');
2 |
3 | const htmlPlugin = new HtmlWebPackPlugin({
4 | template: './client/src/index.html'
5 | });
6 |
7 | module.exports = {
8 | entry: './client/src/index.js',
9 | module: {
10 | rules: [
11 | {
12 | test: /\.js$/,
13 | exclude: /node_modules/,
14 | use: {
15 | loader: 'babel-loader',
16 | options: {
17 | presets: ['@babel/preset-react'],
18 | plugins: [
19 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
20 | ]
21 | }
22 | }
23 | },
24 | {
25 | test: /\.css$/,
26 | use: ['style-loader', 'css-loader']
27 | }
28 | ]
29 | },
30 | plugins: [htmlPlugin]
31 | };
32 |
--------------------------------------------------------------------------------
/client/src/modules/post/providers/AddPost.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { gql } from 'apollo-boost';
3 | import { Mutation } from 'react-apollo';
4 |
5 | import { GET_POSTS } from './PostList';
6 |
7 | const ADD_POST = gql`
8 | mutation($title: String!, $content: String!) {
9 | addPost(title: $title, content: $content) {
10 | title
11 | content
12 | }
13 | }
14 | `;
15 |
16 | const withAddPost = Component => props => {
17 | return (
18 |
19 | {addPost => {
20 | return (
21 | addPost({
22 | variables: { title, content }, refetchQueries: [
23 | { query: GET_POSTS }
24 | ] })}
25 | />
26 | )
27 | }}
28 |
29 | );
30 | };
31 |
32 | export default withAddPost;
--------------------------------------------------------------------------------
/client/src/modules/post/containers/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react'
2 | import { Container, Row, Col } from 'reactstrap';
3 |
4 | import { withPosts } from '../providers';
5 | import { PostList, PostForm } from '../components';
6 |
7 | import '../styles/styles.css';
8 |
9 | /**
10 | * Wrap Posts component using withPosts provider
11 | * for getting posts list in the Posts component
12 | */
13 | @withPosts
14 | export default class PostRoot extends Component {
15 | render() {
16 | const { posts, postsLoading } = this.props;
17 |
18 | return (
19 |
20 | Posts Module
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/client/src/modules/post/components/PostList.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Card, CardTitle, CardBody } from 'reactstrap';
3 |
4 | export default class PostList extends Component {
5 | constructor(props) {
6 | super(props);
7 |
8 | this.showPosts = this.showPosts.bind(this);
9 | }
10 |
11 | showPosts() {
12 | const { posts, postsLoading } = this.props;
13 |
14 | if (!postsLoading && posts.length > 0) {
15 | return posts.map(post => {
16 | return (
17 |
18 | {post.title}
19 | {post.content}
20 |
21 | );
22 | });
23 | } else {
24 | return (
25 |
26 |
No posts available
27 |
Use the form on the right to create a new post.
28 |
29 | );
30 | }
31 | }
32 |
33 | render() {
34 | return (
35 |
36 | {this.showPosts()}
37 |
38 | );
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 SysGears
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 |
--------------------------------------------------------------------------------
/client/src/modules/post/components/PostForm.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import { Form, FormGroup, Label, Input, Button } from 'reactstrap';
4 | import { withAddPost } from '../providers';
5 |
6 | @withAddPost
7 | export default class PostForm extends Component {
8 | constructor(props) {
9 | super(props);
10 | this.submitForm = this.submitForm.bind(this);
11 | }
12 |
13 | submitForm(event) {
14 | event.preventDefault();
15 |
16 | this.props.addPost({
17 | title: event.target.title.value,
18 | content: event.target.content.value
19 | });
20 | }
21 |
22 | render() {
23 | return (
24 |
25 |
Create new post
26 |
37 |
38 | )
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-apollo-express-example",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "server.js",
6 | "scripts": {
7 | "server": "nodemon ./server/server.js",
8 | "client": "webpack-dev-server --mode development --open",
9 | "dev": "concurrently \"npm run client\" \"npm run server\""
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+ssh://git@github.com/sergei-gilevich/react-apollo-express-example.git"
14 | },
15 | "author": "Sergey Hylevich",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/sergei-gilevich/react-apollo-express-example/issues"
19 | },
20 | "homepage": "https://github.com/sergei-gilevich/react-apollo-express-example#readme",
21 | "dependencies": {
22 | "apollo-boost": "^0.1.22",
23 | "apollo-server-express": "^2.2.6",
24 | "bootstrap": "^4.2.1",
25 | "cors": "^2.8.5",
26 | "express": "^4.16.4",
27 | "graphql": "^14.0.2",
28 | "mongoose": "^5.4.1",
29 | "react": "^16.6.3",
30 | "react-apollo": "^2.3.2",
31 | "react-dom": "^16.6.3",
32 | "reactstrap": "^6.5.0"
33 | },
34 | "devDependencies": {
35 | "@babel/core": "^7.2.0",
36 | "@babel/plugin-proposal-decorators": "^7.3.0",
37 | "@babel/preset-react": "^7.0.0",
38 | "babel-loader": "^8.0.4",
39 | "concurrently": "^4.1.0",
40 | "css-loader": "^2.0.0",
41 | "html-webpack-plugin": "^3.2.0",
42 | "nodemon": "^1.18.9",
43 | "react-dev-utils": "^6.1.1",
44 | "style-loader": "^0.23.1",
45 | "webpack": "^4.27.1",
46 | "webpack-cli": "^3.1.2",
47 | "webpack-dev-server": "^3.1.10"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/article.md:
--------------------------------------------------------------------------------
1 | # How to Create an Apollo, React, and Express application
2 |
3 | Creating a React, Express, and Apollo (GraphQL) application can be a serious challenge, and while there are tools like
4 | Create Apollo App that simplify generation of a boilerplate project with
6 | these technologies, it’s best to have a clear understanding how such an application can be built.
7 |
8 | In this tutorial, we show how to create a modular application with the MongoDB, Express, React, Node.js (MERN) stack
9 | that uses Apollo, a popular GraphQL implementation for JavaScript applications.
10 |
11 | You can have a look at the implemented application in this repository:
12 |
13 | * A MERN application with Apollo GraphQL
14 |
15 | Before we dive deep into the implementation details, we want to give a bird view on what this MERN application powered
16 | by Apollo will look like.
17 |
18 | ## An overview of Apollo, React, and Express application
19 |
20 | You’re going to build an application that'll display posts, fetch them from a backend API, and sends new posts to the
21 | server to save them in the database.
22 |
23 | To implement this application, you'll:
24 |
25 | * Create an Express server application that uses Apollo Server;
26 | * Connect the server to MongoDB using Mongoose;
27 | * Build a React client application that uses Apollo Client; and
28 | * Create a Post module on both client and server to handle posts.
29 |
30 | Throughout the project, we install and handle the dependencies with Yarn, but you can use NPM as well. We also
31 | recommend using the latest stable version of Node.js, although any version starting from Node.js 6 will make it.
32 |
33 | We assume that you already have basic understanding of React, Express, MongoDB, and GraphQL. And even if you’re not
34 | familiar with these technologies, with the detailed explanation we give on each code snippet you’ll be able to grasp how
35 | the application works.
36 |
37 | Besides the MERN plus Apollo stack, we use Reactstrap components such as Container, Button, Form, FormControl, and
38 | others to create layouts with generic Bootstrap styles.
39 |
40 | Next, let's review the project structure.
41 |
42 | ### Express, React, Apollo application structure
43 |
44 | Most tutorials provide simplified structure for Express, React, and Apollo applications with the idea to focus on the
45 | implementation rather than how the project looks. However, we're going to show modular approach to bring this
46 | application closer to real-world projects.
47 |
48 | Here’s what the application structure looks like:
49 |
50 | ```
51 | apollo-app
52 | ├── client # The client package
53 | │ └── src # All the source files of the React app
54 | │ ├── config # The React application settings
55 | │ ├── modules # The client-side modules
56 | │ ├── App.js # The React application's main component
57 | │ ├── index.html # React application template
58 | │ └── index.js # React application entry point
59 | ├── node_modules # Global Node.js modules
60 | ├── server # The server package
61 | │ ├── config # The Express application configurations
62 | │ ├── modules # Server modules
63 | │ └── server.js # The Express application's entry point
64 | ├── package.json # Project metadata and packages
65 | └── webpack.config.js # Webpack configuration for React
66 | ```
67 |
68 | The application will have two separate directories — `server` and `client` — to store the server and client
69 | files respectively. This structure also has a single `package.json` file, which will contain all the dependencies. In a
70 | real-world application, however, we recommend that you completely separate the client from the server so they have their
71 | own dependencies and `package.json` files. We don't create two `package.json` files for simplicity.
72 |
73 | Now, we can focus on implementing the application, and we start off with installing MongoDB. After that, we switch to
74 | creating an Express server application that uses Apollo Server to handle GraphQL requests. In the last section, we
75 | review the React application that uses Apollo Client and `react-apollo` to handle GraphQL.
76 |
77 | ## Installing MongoDB
78 |
79 | You can skip over to creating the application if you
80 | already have MongoDB installed and running. If not, run the following commands to install MongoDB. Note that we use the
81 | commands for Ubuntu 18.04, and you need to consult the
82 | official MongoDB documentation for the other installation
84 | options:
85 |
86 | ```sh
87 | # #1 Import the public key used by the package management system
88 | sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 9DA31620334BD75D9DCB49F368818C72E52529D4
89 |
90 | # #2 Create a list file for MongoDB
91 | echo "deb [ arch=amd64 ] https://repo.mongodb.org/apt/ubuntu bionic/mongodb-org/4.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.0.list
92 |
93 | # #3 Reload local package database
94 | sudo apt-get update
95 |
96 | # #4 Install MongoDB
97 | sudo apt-get install -y mongodb-org
98 | ```
99 |
100 | Once the installation is complete, you can check if the MongoDB server is up and running:
101 |
102 | ```sh
103 | # Verify the status of MongoDB server
104 | sudo systemctl status mongod
105 | ```
106 |
107 | After running the command, you should see a message similar to this: `Active: active (running) since Tue 2019-01-08 08:33:00 EET; 51min ago`.
108 | However, if you see the message `Active: inactive (dead)`, then you need to run one of the commands below:
109 |
110 | ```sh
111 | # Restart MongoDB
112 | sudo service mongod restart
113 |
114 | # Or always start MongoDB on the system startup
115 | sudo systemctl enable mongod
116 | ```
117 |
118 | The first command restarts the MongoDB server, while the second command configures MongoDB to always run on the system
119 | startup.
120 |
121 | Now we can focus on creating the application.
122 |
123 | ## Initializing an Express, React, Apollo application
124 |
125 | Initialize a new project by running the commands below:
126 |
127 | ```bash
128 | # #1 Create a new folder for the project
129 | mkdir apollo-app
130 |
131 | # #2 Go into the project
132 | cd apollo-app
133 |
134 | # #3 Initialize a new project
135 | # Use --yes to create a default package.json
136 | yarn init --yes
137 | ```
138 |
139 | You get a directory `apollo-app` (or whatever you named it) and a `package.json` file with some default project
140 | metadata. (Our choice for the project name is dictated by the application design: This application uses Apollo with
141 | GraphQL instead of a traditional RESTful approach.)
142 |
143 | In the next section, you'll create an Express application with Apollo Server.
144 |
145 | ## Creating an Express application with Apollo Server
146 |
147 | An Express application you're going to build has the following structure:
148 |
149 | ```
150 | apollo-app
151 | ├── server # The server package
152 | │ ├── config # The Express application configurations
153 | │ │ └── database.js # Mongoose configurations
154 | │ ├── modules # Server modules
155 | │ │ └── post # The Post module
156 | │ │ ├── models # Mongoose models for Post module
157 | │ │ │ └── post.model.js # Mongoose Post model
158 | │ │ ├── graphqlSchema.js # GraphQL types and mutations
159 | │ │ └── resolvers.js # GraphQL resolver functions
160 | │ └── server.js # The Express application entry point
161 | ```
162 |
163 | The `server.js` is an entry point for this Express application; in this file, an Express app and Apollo Server are
164 | initialized. The `config` folder will contain all the Express application configurations. For example, the mongoose
165 | configurations will be stored in `server/config/database.js`.
166 |
167 | The server application also has the `modules` folder to store all Express modules. In this application, there's just one
168 | module Post, which will have a `Post` model, a GraphQL schema created with Apollo Server, and resolvers to handle
169 | GraphQL queries. Similarly, you can create other server modules under `modules` with their own models, schemas, and
170 | resolvers.
171 |
172 | Let's create an Express server application with this structure.
173 |
174 | ### Installing dependencies for an Express server
175 |
176 | Run the following command to create the `server` directory under the root `apollo-app`:
177 |
178 | ```bash
179 | # Make sure you are in the apollo-app root folder
180 | mkdir server
181 | ```
182 |
183 | That’ll create a space for an Express server application. It’s time to install a few dependencies for Express, Apollo,
184 | and MongoDB.
185 |
186 | Run the following two commands in succession to install all the Express dependencies that the application needs:
187 |
188 | ```bash
189 | # #1 Install the basic Express application dependencies
190 | yarn add express apollo-server-express graphql mongoose
191 |
192 | # #2 Install nodemon to development dependencies
193 | yarn add nodemon --dev
194 | ```
195 |
196 | Let’s clarify what all these packages are used for. Naturally, `express` creates an Express server application. To build
197 | a GraphQL schema and resolvers with Apollo Server, `apollo-server-express` must be installed with `graphql`. Finally,
198 | you need to install `mongoose`, a package that connects to and can query a MongoDB database.
199 |
200 | With the second command, you install `nodemon`, an optional package, which is great in that it simplifies your work
201 | with Express applications — `nodemon` automatically re-builds and re-runs the application whenever you change its
202 | code.
203 |
204 | ### Creating a script to run an Express app
205 |
206 | Provided that you installed nodemon, you can add the following script to the `package.json` file:
207 |
208 | ```json
209 | {
210 | "scripts": {
211 | "server": "nodemon ./server/server.js"
212 | }
213 | }
214 | ```
215 |
216 | Next, we create `server.js`.
217 |
218 | ### Creating an entry point for an Express server application
219 |
220 | Create `server.js` under the `server` folder and add the basic code below:
221 |
222 | ```javascript
223 | // #1 Import Express and Apollo Server
224 | const express = require('express');
225 | const { ApolloServer } = require('apollo-server-express');
226 |
227 | // #2 Import mongoose
228 | const mongoose = require('./config/database');
229 |
230 | // #3 Import GraphQL type definitions
231 | const typeDefs = require('./modules/post/graphqlSchema');
232 |
233 | // #4 Import GraphQL resolvers
234 | const resolvers = require('./modules/post/resolvers');
235 |
236 | // #5 Create an Apollo server
237 | const server = new ApolloServer({ typeDefs, resolvers });
238 |
239 | // #6 Create an Express application
240 | const app = express();
241 |
242 | // #7 Use the Express application as middleware in Apollo server
243 | server.applyMiddleware({ app });
244 |
245 | // #8 Set the port that the Express application will listen to
246 | app.listen({ port: 3000 }, () => {
247 | console.log(`Server running on http://localhost:${port}${server.graphqlPath}`);
248 | });
249 | ```
250 |
251 | Let’s explain what’s happening in the code snippet.
252 |
253 | First, we import `express` and `apollo-server-express` to be able to create an Express application and an instance of
254 | Apollo Server respectively. We also import a mongoose instance from the `config/database.js` file to connect to MongoDB
255 | — the file doesn’t exist yet, but we'll create it soon.
256 |
257 | Next, we import the Apollo type definitions and resolvers — `typeDefs` and `resolvers`. They’re the two key
258 | components necessary for Apollo to work. We also create these components in the later sections.
259 |
260 | After importing the dependencies, we create an instance of Apollo Server and pass it the `typeDefs` and `resolvers`. An
261 | Apollo server will then be able to handle GraphQL queries coming from the client application and using suitable GraphQL
262 | types and resolvers.
263 |
264 | Notice that the Express application must be passed as middleware to the Apollo server. That's necessary so that once
265 | Apollo handles GraphQL queries, it can cede the control to Express.
266 |
267 | Finally, we set the application port and log out a message when the Express application is up and running.
268 |
269 | You might run the Express server right now, but the console will yell with errors: a mongoose instance, type
270 | definitions, and resolvers don’t exist. So let's create them.
271 |
272 | ### Creating a mongoose instance
273 |
274 | To connect to MongoDB, you first need to configure mongoose. The database configs will be kept in a dedicated folder
275 | `server/config`. Create `database.js` with the following code under `server/config`:
276 |
277 | ```javascript
278 | // The file server/config/database.js
279 | // #1 Import mongoose
280 | const mongoose = require('mongoose');
281 |
282 | // #2 Create a query string to connect to MongoDB server
283 | const DB_URI = 'mongodb://localhost:27017/graphql-app';
284 |
285 | // #3 Connect to MongoDB
286 | mongoose.connect(DB_URI, { useNewUrlParser: true });
287 |
288 | // #4 Add basic event listeners on the mongoose.connection object
289 | mongoose.connection.once('open', () => console.log('Connected to a MongoDB instance'));
290 | mongoose.connection.on('error', error => console.error(error));
291 |
292 | // #5 Export mongoose. You’ll use it in server/server.js file
293 | module.exports = mongoose;
294 | ```
295 |
296 | In the code above, we first require mongoose and create a sort of default URI `mongodb://localhost:27017/apollo-app` to
297 | connect to the `apollo-app` database in MongoDB. There’s no need to manually create this database: MongoDB will create
298 | it automatically once you send your first query using mongoose. You can choose any name for a database; we decided to
299 | name it `apollo-app`, the same as the project.
300 |
301 | After requiring mongoose and creating a URI, the application connects to the database by calling the
302 | `mongoose.connect()` method. You must passing `DB_URI` as the first argument to `connect()`. Also, pass a second
303 | argument `{ useNewUrlParser: true }` to enable mongoose’s URL parser. This argument is necessary to remove the warning
304 | `DeprecationWarning: current URL string parser is deprecated`, which may produce with the latest mongoose versions.
305 |
306 | We also add a couple of event listeners for the `open` and `error` events on the `mongoose.connection` object, which you
307 | can understand as a _database the application connected to_, and log out a couple of messages.
308 |
309 | Now, if you run the Express application, it’ll we able connect to MongoDB using the mongoose instance: recall the line
310 | `const mongoose = require('./config/database');` in `server.js`.
311 |
312 | The post module we create next.
313 |
314 | ### Creating a post module for server application
315 |
316 | Because the application has modular structure, you need to create a new folder `modules` under `server` to keep all the
317 | backend modules in one place. And since each module should be stored in its own folder, create `modules/post` for the
318 | Post module. Now you have `server/modules/post` to store all files relevant for the module. We create a mongoose `Post`
319 | model, GraphQL schema, and resolvers one by one in the following sections.
320 |
321 | #### Creating a mongoose model for posts
322 |
323 | With mongoose, creating a model to query MongoDB for data is easy. You first need to describe a mongoose schema to
324 | specify the fields that each new object must have. After that, you can create a model using the `model()` method, and
325 | the model will be used to create new objects to be stored in the database.
326 |
327 | We suggest that you store all models under `server/modules/{module}/models` directories. For this reason, the Post
328 | module will have its models under `server/modules/post/models`.
329 |
330 | Here’s the code for a simple mongoose schema and model for posts:
331 |
332 | ```javascript
333 | // #1 Import the constructor Schema and the model() method
334 | // Note the use of ES6 desctructuring
335 | const { Schema, model } = require('mongoose');
336 |
337 | // #2 Create a post schema using mongoose Schema
338 | const postSchema = new Schema({
339 | title: String,
340 | content: String
341 | });
342 |
343 | // #3 Create a post model with mongoose model() method
344 | const Post = model('post', postSchema);
345 |
346 | module.exports = Post;
347 | ```
348 |
349 | As you can see, a mongoose Schema class is used with an object parameter. In case with posts, the object parameter sets
350 | the two fields — `title` and `content` — both of types `String`.
351 |
352 | To create a new model, it’s enough to call the mongoose `model()` method passing it a model name as the first argument
353 | and a schema as the second argument. The model name must be singular and lowercase letters: `'post'`. Mongoose, however,
354 | will create the _posts_ collection (note the plural form) in the `apollo-app` database.
355 |
356 | There are no resolvers with a GraphQL schema. Let's build them.
357 |
358 | #### Creating a GraphQL schema for posts
359 |
360 | Let’s first recap how GraphQL works. GraphQL needs a schema to understand how to retrieve and save data (posts in this
361 | application). Don’t confuse a _GraphQL_ schema and a _mongoose_ schema, though! These are two different objects that
362 | serve different purposes.
363 |
364 | You can think of a GraphQL schema as of an object that contains the descriptions of the types, queries, and mutations:
365 |
366 | * Types define the shape for data that the server and client send back and forth.
367 | * Queries define how the Express server must respond to the client’s GET requests.
368 | * Mutations determine how the new data is created or updated.
369 |
370 | With the obvious stuff out of the way, we can switch to creating a GraphQL schema for the Post module using Apollo.
371 |
372 | Add a new file `graphqlSchema.js` under the `server/modules/post` folder. Add the following code to the file:
373 |
374 | ```javascript
375 | // #1 Import the gql method from apollo-server-express
376 | const { gql } = require('apollo-server-express');
377 |
378 | // #2 Construct a schema with gql and using the GraphQL schema language
379 | const typeDefs = gql`
380 | #3 Define the type Post with two fields
381 | type Post {
382 | _id: ID,
383 | title: String,
384 | content: String
385 | },
386 | #4 Define the query type that must respond to 'posts' query
387 | type Query {
388 | posts: [Post]
389 | },
390 | #5 Define a mutation to add new posts with two required fields
391 | type Mutation {
392 | addPost(title: String!, content: String!): Post,
393 | }
394 | `;
395 |
396 | module.exports = typeDefs;
397 | ```
398 |
399 | To create type definitions, Apollo's `gql` method is used.
400 |
401 | First in the schema description, we create a type `Post` to tell GraphQL that we’ll be passing around post objects. Each
402 | post will have three fields — an ID, a title, and a content. Each `Post` field must have its own type, and so we
403 | used an `ID` type for `_id` and a `String` type for `title` and `content`.
404 |
405 | Second, the schema contains a query type. In terms of the RESTful design, you can understand `type Query` as an endpoint
406 | GET for a URL `http://my-website.com/posts`. Basically, with `type Query { posts: [Post] }` you say, _"My Express server
407 | will be able to accept GET requests on `posts`, and it must return an array of Post objects on those GraphQL requests."_
408 |
409 | Our GraphQL schema also has a mutation `addPost()` to save users’ posts. This mutation specifies that it needs the
410 | frontend application to send a new post’s title and content. Also, an object of the `Post` type will be returned once
411 | this mutation is done.
412 |
413 | You might have noticed that the mutation doesn’t use the `_id` field, but you don’t have to create this field on the
414 | frontend as MongoDB automatically generates an ID for each document you insert into a database, and that ID will be sent
415 | from the server to the client.
416 |
417 | It’s time to create the second part of the parameter object, resolvers, which must be passed to `ApolloServer` to
418 | process the queries and mutations described in this schema.
419 |
420 | #### Implementing GraphQL resolvers with Apollo
421 |
422 | A GraphQL schema with queries and mutations doesn’t really do anything other than outlining what data types are
423 | available and how to respond to different HTTP requests. To actually handle the client requests, Apollo Server needs
424 | resolvers, which are just functions that retrieve or mutate the requested data.
425 |
426 | The Post module needs another file to store its resolvers, `resolvers.js`, which you can add next to `typeDefs.js` under
427 | the `server/modules/post` directory.
428 |
429 | The code snippet below has two groups of resolvers, Query and Mutation. Also notice the imported mongoose Post model
430 | that you’ve already created, which is necessary to work with posts that are stored in MongoDB:
431 |
432 | ```javascript
433 | // #1 Import the Post model created with mongoose
434 | const Post = require('./models/post');
435 |
436 | // #2 Create resolver functions to handle GraphQL queries
437 | /**
438 | * Query resolver "posts" must return values in response to
439 | * the query "posts" in GraphQL schema.
440 | */
441 | const resolvers = {
442 | Query: {
443 | // Query which returns posts list
444 | posts: () => Post.find({}),
445 | },
446 |
447 | /**
448 | * Mutation resolver addPost creates a new document in MongoDB
449 | * in response to the "addPost" mutation in GraphQL schema.
450 | * The mutation resolvers must return the created object.
451 | */
452 | Mutation: {
453 | addPost: (parent, post) => {
454 | // Create a new post
455 | const newPost = new Post({ title: post.title, content: post.content });
456 | // Save new post and return it
457 | return newPost.save();
458 | }
459 | }
460 | };
461 |
462 | module.exports = resolvers;
463 | ```
464 |
465 | Let’s focus on the resolvers.
466 |
467 | First, you create an object with two fields — `Query` and `Mutation`. When you want to respond back to the client
468 | using Apollo, you need to specify the `Query` property with one or several resolvers for all the query types listed in a
469 | GraphQL schema. In our application, `Query` needs only one resolver `posts()` to respond to the query `posts` defined in
470 | `./post/graphqlSchema.js`. `posts()` will then request MongoDB for posts using the `Post` model’s `find()` method, which
471 | is available on all mongoose models.
472 |
473 | Similarly, when you’re saving a new post with Apollo, you need to add a dedicated `addPost()` resolver to the `Mutation`
474 | property. The `addPost()` resolver accepts two parameters (in fact, it can accept up to _four_ parameters according to
475 | Apollo documentation, but we don’t need all of them for our example).
476 |
477 | The first parameter in `addPost()`, called `parent`, refers to the parent resolver when you have nested resolvers. This
478 | parameter isn’t used for this mutation. The second parameter is the post data sent by the client application, and this
479 | data can be passed to a mongoose model when instantiating new posts:
480 | `new Post({ title: post.title, content: post.content })`.
481 |
482 | Pay attention to the code inside `addPost()`. Just instantiating a new document with a mongoose model doesn’t save it to
483 | the database, and you have to additionally call the model method `save()` on the newly created object. Also remember
484 | to return the object in the mutation resolver so that Express sends it back to the client (`save()` returns a new
485 | document after saving it to MongoDB).
486 | ___
487 |
488 | Let’s recap what you’ve created so far:
489 |
490 | * A very simple Express application
491 | * An instance of Apollo Server to handle GraphQL on the server
492 | * A connection to a MongoDB instance with mongoose
493 | * A Post module with a model, GraphQL schema, and mutations
494 |
495 | This is what the console output should produce when you now run the Express application with `yarn server`:
496 |
497 | ```sh
498 | λ yarn server
499 | yarn run v1.13.0
500 | $ nodemon server/server.js
501 | [nodemon] 1.18.9
502 | [nodemon] to restart at any time, enter `rs`
503 | [nodemon] watching: *.*
504 | [nodemon] starting `node server/server.js`
505 | Server is running on http://localhost:3000/graphql
506 | Connected to MongoDB
507 | ```
508 |
509 | It’s time to start building a React application that will connect to the Express server using Apollo Client.
510 |
511 | ## Creating a client application with React and Apollo Client
512 |
513 | To store the React application code, you need to create the `client/src` folder under the root:
514 |
515 | ```sh
516 | # apollo-app root folder
517 | mkdir client
518 | cd client
519 | mkdir src
520 | ```
521 |
522 | We again start with a discussion over the modular structure for the React application. You can skip the next section and
523 | start building the application React, though.
524 |
525 | ### React and modular architecture
526 |
527 | In most cases, you build a React application like this: You create an entry point `index.js`, a root component `App`,
528 | and a `components` folder to store all kinds of React components. But the client application in this guide will have
529 | different structure reflecting the modular approach we used for our server application:
530 |
531 | ```
532 | graphql-app
533 | ├── client # The frontend package
534 | │ └── src # All the source files of the React app
535 | │ ├── modules # The React modules
536 | │ │ ├── post # The Post module
537 | │ │ ├── user # The User module
538 | │ │ └── auth # The Authentication module
539 | │ ├── config # React application configurations
540 | │ │ └── createApolloClient.js # An Apollo Client instance
541 | │ ├── App.js # The React application root component
542 | │ ├── index.html # The React application HTML layout
543 | │ └── index.js # The entry point for React application
544 | ```
545 |
546 | The application will have the `modules` folder with individual folders for each module: Post, User, and Auth. (We build,
547 | however, only a Post module in this guide.) Additionally, the folder with all modules must contain an `index.js` file
548 | that exports them:
549 |
550 | ```javascript
551 | export { Posts } from './post';
552 | export { User } from './user';
553 | export { Auth } from './auth';
554 | ```
555 |
556 | You can then import all the modules into `App.js` and add them to the root component to be rendered.
557 |
558 | Recalling that life isn't all beer and skittles, we make the structure even more complex by creating several folders in
559 | each module to keep module files grouped by their purpose.
560 |
561 | This is what the Post module look like (and other modules should be created with the same structure in mind):
562 |
563 | ```
564 | modules
565 | ├── post
566 | │ ├── components
567 | │ ├── containers
568 | │ ├── providers
569 | │ ├── styles
570 | │ └── index.js
571 | ```
572 |
573 | Let’s clarify what's what in the module:
574 |
575 | * The `components` folder contains separate module components. Their only purpose is to render parts of the module. In
576 | our application, `components` will store `PostList` and `PostForm` React components.
577 | * `containers` will have just one file `index.js` with a root module container to render the module components.
578 | `index.js` will therefore import all the necessary components from `components`.
579 | * `providers` will contain wrappers for components. Because components shouldn’t query a server application for data,
580 | you should delegate this responsibility to providers. Recall the Separation of Concerns design principle, and it’ll
581 | become clear why we use providers.
582 | * `styles` will contain a `styles.css` files with module styles.
583 | * `index.js` exports an entire module.
584 |
585 | Although the approach of creating complicated modular structure may seem useless and mind-blowing for a small React
586 | application, it’ll bear fruits when your application grows. It’s a good idea to have good structure from the very
587 | beginning of application development.
588 |
589 | You can now focus on creating the React application.
590 |
591 | ### Installing React, Apollo, and webpack dependencies
592 |
593 | You need to install quite a few dependencies to be able to run a React application.
594 |
595 | For starters, since the React builds will be created with webpack, install the webpack dependencies along with loaders
596 | and plugins to development dependencies (read: `devDependencies` in `package.json`).
597 |
598 | ```sh
599 | # Run from the root
600 | yarn add webpack webpack-cli webpack-dev-server --dev
601 | ```
602 |
603 | The `webpack` and `webpack-cli` are the basic dependencies necessary to build bundles with webpack, and
604 | `webpack-dev-server` is necessary to serve the frontend applications.
605 |
606 | Now install these dependencies:
607 |
608 | ```
609 | yarn add html-webpack-plugin css-loader style-loader babel-loader --dev
610 | ```
611 |
612 | With `html-webpack-plugin`, you can automate creation of the `index.html` root layout for the React application; this
613 | plugin automatically adds the `index.js` entry point to the `script` tag in the layout. The React application also needs
614 | two loaders for handle CSS — `style-loader` and `css-loader`.
615 |
616 | `babel-loader`, which traspiles React code to valid JavaScript, requires two additional Babel dependencies to work
617 | — `@babel/core` and `babel/preset-react`. And since we’ll be using class decorators for React components, the
618 | `@babel/plugin-proposal-decorators` is also necessary (we’ll explain what decorators do later in the article when we
619 | have a look at the Post module components).
620 |
621 | Install the following three dependencies:
622 |
623 | ```sh
624 | yarn add @babel/core @babel/preset-react @babel/plugin-proposal-decorators --dev
625 | ```
626 |
627 | You can now install React:
628 |
629 | ```sh
630 | yarn add react react-dom
631 | ```
632 |
633 | Finally, to be able to use GraphQL with React, another three dependencies are required — `apollo-boost`,
634 | `react-apollo`, and `graphql` (you've already installed `graphql` for the server application, so there's no need to
635 | install it again):
636 |
637 | ```bash
638 | yarn add apollo-boost react-apollo
639 | ```
640 |
641 | `apollo-boost` is an all-in-one package provided by Apollo to let you build GraphQL queries and mutations on the client.
642 | React needs `react-apollo`, a library that provides custom GraphQL components, in particular, `ApolloProvider`, `Query`,
643 | and `Mutation`. These GraphQL components will be necessary to wrap the React layout components yielding them the data
644 | retrieved from the server by Apollo.
645 |
646 | We’re also going to use a UI toolkit Reactstrap to quickly create Bootstrap components with generic styles. Reactstrap
647 | is optional, but you’ll have to manually create the layout for your React application components without it. It's your
648 | call.
649 |
650 | Since Reactstrap can't work without Bootstrap, you need to install two dependencies:
651 |
652 | ```sh
653 | yarn add reactstrap bootstrap
654 | ```
655 |
656 | That's it. All the dependencies for a React client application are in place. Before we actually start creating a React
657 | application, let’s also add a script to be able to run it:
658 |
659 | ```json
660 | {
661 | "scripts": {
662 | "client": "webpack-dev-server --mode development --open"
663 | }
664 | }
665 | ```
666 |
667 | This script will run `webpack-dev-server` in development mode. And once the client build is ready, your default browser
668 | will automatically open the page: notice the `--open` option.
669 |
670 | Don’t you try to run the React application _now_ as errors will ensue. You need to first configure webpack for React,
671 | then create React application basic files, and, finally, create a Post module.
672 |
673 | ### Configuring webpack for React
674 |
675 | Create `webpack.config.js` under the project’s root folder and add the code below. There aren't many configurations as
676 | we intentionally keep the webpack configuration minimal:
677 |
678 | ```javascript
679 | const HtmlWebPackPlugin = require('html-webpack-plugin');
680 |
681 | const htmlPlugin = new HtmlWebPackPlugin({
682 | template: './client/src/index.html'
683 | });
684 |
685 | module.exports = {
686 | entry: './client/src/index.js',
687 | module: {
688 | rules: [
689 | {
690 | test: /\.js$/,
691 | exclude: /node_modules/,
692 | use: {
693 | loader: 'babel-loader',
694 | options: {
695 | presets: ['@babel/preset-react'],
696 | plugins: [
697 | ["@babel/plugin-proposal-decorators", { "legacy": true }],
698 | ]
699 | }
700 | }
701 | },
702 | {
703 | test: /\.css$/,
704 | use: ['style-loader', 'css-loader']
705 | }
706 | ]
707 | },
708 | plugins: [htmlPlugin]
709 | };
710 | ```
711 |
712 | This webpack configuration is typical for React applications.
713 |
714 | Webpack needs the entry to know where the application starts and the `module.rules` property to know how to handle
715 | different files. All the `.js` files will be processed with the `babel-loader` and `@babel/preset-react`. Additionally,
716 | we set the `plugins` property to use `@babel/plugin-proposal-decorators` and `{'legacy': true}`, which are default Babel
717 | settings for decorators. Notice that `HTMLWebpackPlugin` must be passed to the `plugins` array so that webpack knows
718 | what HTML file to load with the build script.
719 |
720 | The webpack configuration is done. It’s time to work on our React application.
721 |
722 | ### Creating basic files for a React application
723 |
724 | You can finally move on to building a React application.
725 |
726 | Create the `client/src` folder under the root directory of the project if you haven’t done this already. Inside the
727 | `src` folder, create the other two: `config` to store the configurations and `modules` to store the post module.
728 |
729 | Next, you need to create the following key files for React:
730 |
731 | * `index.html`, a basic HTML template
732 | * `index.js`, the entry point
733 | * `App.js`, the root React component
734 | * `settings/createApolloClient.js`, an instance of Apollo Client with configurations
735 |
736 | Let’s create the listed files.
737 |
738 | Add the following HTML code into `client/src/index.html`:
739 |
740 | ```html
741 |
742 |
743 |
744 |
745 |
746 |
747 |
748 | An Apollo, React, and Express Example Application
749 |
750 |
751 |
752 |
753 |
754 |
755 |
756 | ```
757 |
758 | Note that there's no script tag with the link to the `index.js` file because `HtmlWebpackPlugin` adds it for you.
759 |
760 | Next, create the entry point for the application — the `client/src/index.js` file. Add the following code to
761 | bootstrap the React application:
762 |
763 | ```javascript
764 | import React from 'react'
765 | import ReactDOM from 'react-dom';
766 |
767 | import App from './App';
768 |
769 | import 'bootstrap/dist/css/bootstrap.min.css';
770 |
771 | ReactDOM.render(, document.getElementById('root'));
772 | ```
773 |
774 | This code is typical for any React application: You import the root `App` component, two required React dependencies,
775 | and Bootstrap. Finally, you render `App` into the `div#root` HTML element (found in `index.html`).
776 |
777 | ### Creating the main React App component
778 |
779 | You might have already created root `App` components for your React applications with the RESTful approach, so you know
780 | what a React’s root component looks like. In this application, however, `App` must be wrapped into another component,
781 | which is provided by `react-apollo` to make it possible to use Apollo Client with React.
782 |
783 | Let’s take a look at the code in `App.js`:
784 |
785 | ```javascript
786 | import React, { Component } from 'react'
787 | import { ApolloProvider } from 'react-apollo';
788 | import apolloClient from './settings/createApolloClient';
789 | import { Posts } from './modules';
790 |
791 | class App extends Component {
792 | render() {
793 | return (
794 |
795 |
796 |
797 | )
798 | }
799 | }
800 |
801 | export default App;
802 | ```
803 |
804 | As you can see, besides the basic React dependencies — `React` and `Component`, you need to import
805 | `ApolloProvider`, an instance of Apollo Client, and the Post module as `Posts`.
806 |
807 | `ApolloProvider` from `react-apollo` is a root component to wrap an entire React application, and it needs an instance
808 | of Apollo Client to work: ``. An Apollo Client instance will tell the application
809 | how to query the server.
810 |
811 | Now we need to create an Apollo Client instance.
812 |
813 | ### Initializing Apollo Client
814 |
815 | Create a directory `config` under `client/src`, then add `createApolloClient.js` with the following code into `config`:
816 |
817 | ```javascript
818 | import ApolloClient from 'apollo-boost';
819 |
820 | const client = new ApolloClient({
821 | uri: 'http://localhost:3000/graphql'
822 | });
823 |
824 | export default client;
825 | ```
826 |
827 | There’s nothing special going on in this file. First, you import the `ApolloClient` constructor from `apollo-boost`.
828 | Then, when instantiating an Apollo client, you need to pass an object parameter with Apollo settings. For this
829 | application, only the URL is necessary so that Apollo knows where to send queries. (You may run the Express application
830 | and see the logged line `Server is running on http://localhost:3000/graphql`, the same URL.)
831 |
832 | ## Creating a React module
833 |
834 | Under the `client/src/modules` folder, create a new directory `post` to keep the post module. Next, create another four
835 | sub-directories under `post`: `components`, `containers`, `providers`, and `styles`. Finally, add `index.js` with the
836 | following code under `post`:
837 |
838 | ```javascript
839 | export { default as Posts } from './containers';
840 | ```
841 |
842 | As you can see, `client/src/modules/post/index.js` imports all the code from `./containers`. Create an `index.js` under
843 | `client/src/modules/post/containers` and copypaste the code below into it:
844 |
845 | ```javascript
846 | import React, { Component } from 'react'
847 |
848 | import { withPosts } from '../providers';
849 | import { PostList, PostForm } from '../components';
850 |
851 | import { Container, Row, Col } from 'reactstrap';
852 | import '../styles/styles.css';
853 |
854 | /**
855 | * Wrap Posts component using the withPosts provider
856 | * to get data retrieved with GraphQL.
857 | */
858 | @withPosts
859 | export default class PostRoot extends Component {
860 | render() {
861 | const { posts, postsLoading } = this.props;
862 |
863 | return (
864 |
865 | Posts Module
866 |
867 |
868 |
869 |
870 |
871 |
872 |
873 |
874 |
875 |
876 | )
877 | }
878 | }
879 | ```
880 |
881 | Let’s clarify how this code works.
882 |
883 | `post/containers/index.js` provides a root React component for the post module and is called `PostRoot`. Besides
884 | importing `React` and `Component`, we also imported the main provider `withPosts` (which we show later). `withPosts`
885 | wraps `PostRoot` and provides the posts received from the server into the `PostList` component.
886 |
887 | Note the syntax: before the class definition goes `@withPosts` as a _decorator_, and for this exact functionality you
888 | installed `@babel/plugin-proposal-decorators`.
889 |
890 | Instead of using a decorator, you’d have to write code like this:
891 |
892 | ```js
893 | //…
894 | class PostRoot extends Component { //… }
895 |
896 | export default withPosts(PostRoot);
897 | ```
898 |
899 | As you can see, using decorators is a bit shorter and more concise than writing `withPosts(PostsRoot)`.
900 |
901 | Let’s now discuss the layout in this container. As you noticed, we imported `Container`, `Row`, and `Col` components
902 | from Reactstrap. Each Reactstrap component is an HTML layout with default Bootstrap classes. Therefore, the following
903 | two layouts are identical:
904 |
905 | ```html
906 |
907 |
908 | Post Module
909 |
910 |
911 |
912 |
913 |
914 |
915 |
916 |
917 |
918 |
919 |
920 |
921 |
922 |
Post Module
923 |
924 |
925 |
926 |
927 |
928 |
929 |
930 |
931 |
932 |
933 | ```
934 |
935 | Reactstrap makes building React layouts with Bootstrap styles simpler than if you’d write basic HTML with Bootstrap
936 | classes manually.
937 |
938 | There’s one more thing to do before you can create providers and components for the post module. For convenience, it’s
939 | best to have `index.js` files under each module folder to exports multiple providers or components.
940 |
941 | Create an `index.js` file under `post/providers` and add another `index.js` into `post/components`.
942 |
943 | This is what the `post/providers/index.js` file should look like:
944 |
945 | ```javascript
946 | export { default as withPosts } from './PostList';
947 | export { default as withAddPost } from './AddPost';
948 | ```
949 |
950 | And this how `post/components/index.js` looks:
951 |
952 | ```javascript
953 | export { default as PostList } from './PostList';
954 | export { default as PostForm } from './PostForm';
955 | ```
956 |
957 | Thanks to such exports, you can shorten the import statements in your files. For instance, instead of importing a
958 | specific component with a line `import { withPosts } from '../providers/withPosts';` you can write a simpler line
959 | `import { withPosts } from '../providers';`. This way, you can also import _multiple_ components in a single line.
960 |
961 | Now you can focus on creating the application components and providers. First, we talk through creating the Post List
962 | part of the post module, and after that we’ll show Post Form.
963 |
964 | ### Creating a Post List
965 |
966 | The Post List part will be responsible for two things — retrieving posts from the server and rendering them.
967 | Hence, you need to create two separate React components to handle those two functions.
968 |
969 | One component will only render the list; this component will be stored in `post/components/PostList.js`. Another
970 | component is created according to the Higher Order Component (HOC) pattern. An HOC for Post List, stored in
971 | `post/providers/PostList.js`, will use Apollo to query the server and pass posts as props to the dumb component.
972 |
973 | #### Creating a `PostList` Higher Order Component
974 |
975 | For a recap, HOC is a pattern in React development that lets you reuse the same logic for many components. In simple
976 | terms, an HOC is a React component that does something and then passes the results of calculations as a props object
977 | into a wrapped component that needs those results.
978 |
979 | In this application, a `post/providers/PostList` provider will use Apollo to query the server, get posts, and then pass
980 | those posts to the wrapped component `post/components/PostList`.
981 |
982 | Add the `PostList.js` file with the following code under `post/providers`:
983 |
984 | ```javascript
985 | import React from 'react';
986 | import { gql } from 'apollo-boost';
987 | import { Query } from 'react-apollo';
988 |
989 | export const GET_POSTS = gql`
990 | {
991 | posts {
992 | _id
993 | title
994 | content
995 | }
996 | }
997 | `;
998 |
999 | const withPosts = Component => props => {
1000 | return (
1001 |
1002 | {({ loading, data }) => {
1003 | return (
1004 |
1005 | );
1006 | }}
1007 |
1008 | );
1009 | };
1010 |
1011 | export default withPosts;
1012 | ```
1013 |
1014 | The `withPosts` provider uses the `gql` method from `apollo-boost` and a `Query` wrapper component from `react-apollo`.
1015 |
1016 | Next, a constant `GET_POSTS` is created, and it references a GraphQL query that defines the data that the server
1017 | application must return upon each GET request — a post’s ID, title, and content.
1018 |
1019 | Finally, a `withPosts()` functional React component is created and is then used as a decorator in the `PostRoot`
1020 | container. Notice how `withPosts()` accepts a `Component` parameter and then returns another function. This function
1021 | accepts the props parameter (it may receive props from the higher React components) and returns a `react-apollo` `Query`
1022 | component, which passes a post list, data, and props into `Component` (`PostRoot` is the component to be wrapped by
1023 | `Query`).
1024 |
1025 | It should be clear by now that `post/components/PostList` receives the `postsLoading` and `posts` values through
1026 | `PostRoot`. In its turn, `PostRoot` gets the data from `withPosts` provider. The flow looks like this:
1027 |
1028 | ${img (location: "",
1029 | alt:"React’s Higher Order Component with Container and Component interaction")}
1030 |
1031 | There’s one more thing to explain before we move further: what’s happening in `Query`? In the `Query` wrapper component,
1032 | we need to interpolate a function, which looks like this:
1033 |
1034 | ```js
1035 | {({ loading, data }) => {
1036 | return ();
1037 | }}
1038 | ```
1039 |
1040 | This function accepts an object returned by GraphQL, and this object has two properties: `loading` and `data`. `loading`
1041 | is a boolean value, and `data` contains all the information that came upon a GraphQL query. In this application, `data`
1042 | will contain an array of posts to be rendered by `Component`. Any other props that the rendering component may use also
1043 | should be passed as `{...props}`.
1044 |
1045 | Next, we create a layout component `PostList`.
1046 |
1047 | #### Creating a dumb component for the post list
1048 |
1049 | To render a list of posts, just add `post/components/PostList.js` with the code below to your current project:
1050 |
1051 | ```js
1052 | import React, { Component } from 'react';
1053 | import { Card, CardTitle, CardBody } from 'reactstrap';
1054 |
1055 | export default class PostList extends Component {
1056 | constructor(props) {
1057 | super(props);
1058 |
1059 | this.showPosts = this.showPosts.bind(this);
1060 | }
1061 |
1062 | showPosts() {
1063 | const { posts, postsLoading } = this.props;
1064 |
1065 | if (!postsLoading && posts.length > 0) {
1066 | return posts.map(post => {
1067 | return (
1068 |
1069 | {post.title}
1070 | {post.content}
1071 |
1072 | );
1073 | });
1074 | } else {
1075 | return (
1076 |
1077 |
No posts available
1078 |
Use the form on the right to create a new post.
1079 |
1080 | );
1081 | }
1082 | }
1083 |
1084 | render() {
1085 | return (
1086 |
1087 | {this.showPosts()}
1088 |
1089 | );
1090 | }
1091 | }
1092 | ```
1093 |
1094 | As you can see, this is a typical React component. We use Reactstrap components `Card`, `CardTitle`, and `CardBody` to
1095 | create a layout for each post. The `showPosts()` method first verifies if posts exist, and if they do, it returns a
1096 | layout to render posts. If there are no posts, a default layout is returned.
1097 |
1098 | The Post List part is created. But it doesn’t show anything, se we’re going to create a mutation and a post form to grab
1099 | post data and send it to the server. And once a post gets back from the server, Post List will render it.
1100 |
1101 | ### Creating a post form
1102 |
1103 | To be able to create posts with GraphQL mutations, an HTML form is necessary. Similarly to how you created the Post
1104 | List part of the application, add another two files: `post/providers/AddPost.js` and `post/components/PostForm.js`.
1105 |
1106 | The layout component, `PostForm.js`, will only render a form and submit it with post title and content, while the
1107 | `AddPost` provider will use a GraphQL mutation to send posts to the backend.
1108 |
1109 | #### Creating a Higher Order Component for post form
1110 |
1111 | `AddPost` is also an HOC just like `post/providers/PostList`, and it also uses a `react-apollo` wrapper component,
1112 | although this time the necessary wrapper is `Mutation`, not `Query`. To create a GraphQL mutation, you also need the
1113 | `gql` method from `apollo-boost`.
1114 |
1115 | Here’s the entire code for `AddPost`:
1116 |
1117 | ```javascript
1118 | import React from 'react';
1119 | import { gql } from 'apollo-boost';
1120 | import { Mutation } from 'react-apollo';
1121 |
1122 | import { GET_POSTS } from './PostsList';
1123 |
1124 | const ADD_POST = gql`
1125 | mutation($title: String!, $content: String!) {
1126 | addPost(title: $title, content: $content) {
1127 | title
1128 | content
1129 | }
1130 | }
1131 | `;
1132 |
1133 | const withAddPost = Component => props => {
1134 | return (
1135 |
1136 | {addPost => {
1137 | return (
1138 | addPost({
1139 | variables: { title, content }, refetchQueries: [
1140 | { query: GET_POSTS }
1141 | ] })}
1142 | />
1143 | )
1144 | }}
1145 |
1146 | );
1147 | };
1148 |
1149 | export default withAddPost;
1150 | ```
1151 |
1152 | Similarly to the `PostList` HOC, you need to create a GraphQL mutation, which is `ADD_POST`. This mutation gets two
1153 | values — `title` and `content` — and sends an `addPost` mutation query to the backend.
1154 |
1155 | Now let’s take a look at the `withAddPost()` functional React component. It returns a `Mutation` wrapper component,
1156 | which accepts the `ADD_POST` mutation to be able to send mutation queries.
1157 |
1158 | Let’s also have a closer look at the `Mutation` contents:
1159 |
1160 | ```js
1161 | {addPost => {
1162 | return (
1163 | addPost({
1164 | variables: { title, content }, refetchQueries: [
1165 | { query: GET_POSTS }
1166 | ] })}
1167 | />
1168 | )
1169 | }}
1170 | ```
1171 |
1172 | The interpolated function inside `Mutation` accepts just one argument, the `addPost` mutation, and returns `Component`,
1173 | `PostForm` in our application.
1174 |
1175 | Also notice that `Component` gets an `addPost` prop function, which will be called in `PostForm` with two parameters
1176 | — `title` and `content`, the form data you'll submit.
1177 |
1178 | Inside the component's `addPost()` method there's a call of the `addPost()` mutation passed to the mutation function.
1179 | You can see the following object:
1180 |
1181 | ```js
1182 | {
1183 | variables: { title, content },
1184 | refetchQueries: [{ query: GET_POSTS }]
1185 | }
1186 | ```
1187 |
1188 | The object with `variables` and `refetchQueries` properties is used by GraphQL to handle mutation queries.
1189 |
1190 | First, we just pass the values that must be stored in the database into the `variables` property. Once those values are
1191 | stored to the database, we want the server to respond to the client with the created object. If you just send an
1192 | `addPost` mutation query with post data, you won’t see a new post shown in the list because you’ll have to manually
1193 | refresh the page. This is when `refetchQueries` is helpful. `refetchQueries` is an array of query objects that we want
1194 | to resend. And we want to resend only one query — `GET_POSTS`.
1195 |
1196 | Finally, let’s create a form to get post data.
1197 |
1198 | #### Creating a PostForm component
1199 |
1200 | Add the file `PostForm.js` with the following code into `post/components`:
1201 |
1202 | ```javascript
1203 | import React, { Component } from 'react';
1204 |
1205 | import { Form, FormGroup, Label, Input, Button } from 'reactstrap';
1206 | import { withAddPost } from '../providers';
1207 |
1208 | @withAddPost
1209 | export default class PostForm extends Component {
1210 | constructor(props) {
1211 | super(props);
1212 | this.submitForm = this.submitForm.bind(this);
1213 | }
1214 |
1215 | submitForm(event) {
1216 | event.preventDefault();
1217 |
1218 | this.props.addPost({
1219 | title: event.target.title.value,
1220 | content: event.target.content.value
1221 | });
1222 | }
1223 |
1224 | render() {
1225 | return (
1226 |
1227 |
Create new post
1228 |
1239 |
1240 | )
1241 | }
1242 | }
1243 | ```
1244 |
1245 | `PostForm` is a typical React component. It has the `submitForm()` method to submit post data using the `addPost()`
1246 | method (which it received as a prop from the `AddPost` provider component). The form layout is also typical: We use
1247 | Reactstrap components `Form`, `FormGroup`, `Label`, `Input`, and `Button` to create the an HTML form with Bootstrap.
1248 |
1249 | The React application is almost done. We suggest adding some styles and a script to run both server and client
1250 | applications with one command.
1251 |
1252 | ___
1253 |
1254 | Let’s recap what you’ve created so far:
1255 |
1256 | * A modular React application
1257 | * An instance of Apollo Client to handle GraphQL in React
1258 | * A Post module consisting of Post List and Post Form
1259 | * Providers and components for each module part
1260 | * A mutation and query created with Apollo’s gql method
1261 |
1262 | ## The finishing touches
1263 |
1264 | You may want to run the React and Express applications simultaneously with just one command. That’s easy to achieve:
1265 | install the package called `concurrently` that will let you run two scripts at the same time.
1266 |
1267 | Add `concurrently` to the development dependencies in your `package.json`:
1268 |
1269 | ```bash
1270 | yarn add concurrently --dev
1271 | ```
1272 |
1273 | Now, add another script into the `package.json` to run the client and server applications at the same time:
1274 |
1275 | ```json
1276 | {
1277 | "scripts": {
1278 | "server": "nodemon ./server/server.js",
1279 | "client": "webpack-dev-server --mode development --open",
1280 | "dev": "concurrently \"yarn client\" \"yarn server\""
1281 | }
1282 | }
1283 | ```
1284 |
1285 | Your React, Express, and Apollo application is ready and you can run it with the following command:
1286 |
1287 | ```bash
1288 | yarn dev
1289 | ```
1290 |
1291 | To make the client application look better, add the following code to the `client/src/modules/post/styles/styles.css`
1292 | file:
1293 |
1294 | ```css
1295 | .container {
1296 | margin-top: 25px;
1297 | padding: 25px 15px;
1298 | background-color: #f5f7f9;
1299 | }
1300 |
1301 | .card-body {
1302 | margin-bottom: 20px;
1303 | }
1304 |
1305 | .post-card {
1306 | width: 350px;
1307 | margin: 7px auto;
1308 | cursor: pointer;
1309 | color: #880a8e;
1310 | box-shadow: 1px 2px 3px rgba(136,10,142,0.3);
1311 | }
1312 |
1313 | .post-form {
1314 | padding: 25px 20px;
1315 | border: 1px solid rgba(0,0,0,0.125);
1316 | border-radius: 4px;
1317 | box-shadow: 1px 2px 3px rgba(136,10,142,0.3);
1318 | }
1319 |
1320 | .post-card:hover, .post-form:hover {
1321 | box-shadow: 2px 3px 4px rgba(136,10,142,0.5);
1322 | }
1323 |
1324 | .submit-button {
1325 | background-color: #880a8e !important;
1326 | }
1327 | ```
1328 |
1329 | The application should be re-built and re-run, and you can open the browser with it. Add a post and see how it’s
1330 | rendered to the list:
1331 |
1332 | ${img (location: "",
1333 | alt:"Working React application with Apollo GraphQL")}
--------------------------------------------------------------------------------