├── .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 |Use the form on the right to create a new post.
1079 |Use the form on the right to create a new post.
28 |