├── .gitignore ├── LICENSE ├── article.md ├── client └── src │ ├── App.js │ ├── config │ └── createApolloClient.js │ ├── index.html │ ├── index.js │ └── modules │ ├── index.js │ └── post │ ├── components │ ├── PostForm.js │ ├── PostList.js │ └── index.js │ ├── containers │ └── index.js │ ├── index.js │ ├── providers │ ├── AddPost.js │ ├── PostList.js │ └── index.js │ └── styles │ └── styles.css ├── package.json ├── server ├── config │ └── database.js ├── modules │ └── post │ │ ├── graphqlSchema.js │ │ ├── models │ │ └── post.js │ │ └── resolvers.js └── server.js └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json* 3 | dist/ 4 | .idea 5 | yarn.lock 6 | yarn-error.log 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 |
this.submitForm(event)}> 1229 | 1230 | 1231 | 1232 | 1233 | 1234 | 1235 | 1236 | 1237 | 1238 |
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")} -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | React Apollo Express Example 9 | 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /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')); -------------------------------------------------------------------------------- /client/src/modules/index.js: -------------------------------------------------------------------------------- 1 | export { Posts } from './post'; -------------------------------------------------------------------------------- /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 |
this.submitForm(event)}> 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 |
37 |
38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/index.js: -------------------------------------------------------------------------------- 1 | export { default as Posts } from './containers'; -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /client/src/modules/post/providers/index.js: -------------------------------------------------------------------------------- 1 | export { default as withPosts } from './PostList'; 2 | export { default as withAddPost } from './AddPost'; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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; -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------