The Complete Guide to Full Stack MARN Web Apps Development: MongoDB, Apollo (GraphQL), React, and Node.js
This is a step-by-step overview for creating the front-end, backend, and persistence layer with MARN Stack: MongoDB, Apollo Server, React and Node.js. I also created a video version of this tutorial, which you can find on YouTube.
What is the MARN stack?
MARN stands for MongoDB, Apollo Server, React and Node.js.
- MongoDB — document database
- Apollo Server — GraphQL server
Why Apollo Server instead of Express? Everything has its pros and cons. I like Apollo Server for its ease of setup, GraphiQL console (very useful during development), and support for many front-end frameworks. For more check out Comparison of Apollo Server with
There is a lot of documentation and tutorials for building GraphQL backend with Apollo Server. There is also a lot of documentation for consuming GraphQL API from React. Yet, there is nothing about building end-to-end web apps with React, Apollo Server backend, and MongoDB persistence layer.
I created a video version of this guide:
- Install node.js: I recommend installing node with brew (I’m using node 16.14.2 and npm 8.5.0):
brew install node
- Install nodemon (I am using version 2.0.20):
npm install nodemon
- Install Mongo (instructions for Mac, instructions for Windows). I’m using MongoDB Community Edition 6.0. I recommend installing with brew (on Mac):
xcode-select --install # installing XCode tools brew tap mongodb/brew` brew update` brew install email@example.com
Setup Apollo Server (GraphQL API) with Node.js
Create a new directory for your app:
Create a new directory for Apollo Server
mkdir apollo-server cd apollo-server
npm install @apollo/server graphql-tag npm install @babel/core @babel/node --save-dev
Add script to run Apollo Server with nodemon to
type=module property to
package.json to enable ES 6 modules:
GraphQL schema describes the shape of your available data.
GraphQL resolvers are responsible for populating data into fields in Schema.
The above code defines 1 field (
hello) in GraphQL Schema, and the resolver function returns
"Hello from Apollo Server" when querying that field.
Start Apollo Server with:
If you go to http://localhost:4000 you should see GraphQL Playground where you can execute queries:
Create Web UI with React
Create react app from your project root directory:
npx create-react-app web-ui cd web-ui npm install @apollo/client graphql npm start
Querying Apollo GraphQL API from React
cd web-ui npm install @apollo/client
To call Apollo Server with Apollo Client module, we need to do two things:
Wrap React components with
ApolloProvider component and pass
ApolloClient instance to it:
To query the Apollo GraphQL endpoint we can use
useQuery React hook provided by
Call 'hello' query from React
Hello React component that will perform a call to GraphQL API and display the returned result. We need to define a GraphQL query and pass it to
useQuery. The neat thing about the Apollo server is the ability to directly copy/paste queries from the playground.
To make it work we need to update
App.js with changes mentioned in the previous section: initializing
ApolloCLient and wrapping components with
This should display
Hello from Apollo Server in the browser coming from GraphQL API!
Add parameter to GraphQL query
So far, the query is pretty simple. Let’s make it more sophisticated by adding a parameter
name, and changing the response to
To do that we need to modify GraphQL schema and resolvers in the backend:
Notice that we extract
name param from the second argument of the resolver function.
We can use GraphQL Playground to test it, and help us to generate the query with a parameter:
Call GraphQL query with parameter from React
We need to update our
HELLO_QUERY. You can copy/paste the query from GraphQL Playground:
We also need to pass
name variable to
This should result in displaying
Hello Jacob in the browser:
Refactoring resolvers and schema to separate components
Before we move to the next section, let’s clean up our backend code by extracting schema and resolvers to separate modules.
Create new file
Create new file
Remove equivalent pieces of code from
src/index.js and import modules instead:
This will set us up for the next section. You should always extract independent modules as much as possible to keep your code clean.
CRUD with MongoDB
CRUD stands for Create, Read, Update, Delete. It’s the backbone of every web app. Except Twitter. They didn’t support Update for a while xD
In this section, I’ll describe how to create simple CRUD for books. Every book will have a title and year when it was published. Data will be stored in MongoDB. Web UI will have an interface to display (Read), add (Create), edit (Update) and delete books through GraphQL API.
Working with MongoDB
To start mongo on Mac (docs):
brew services start firstname.lastname@example.org
To get started with Books Library, let’s create new database called
marn and new collection called
books. It can be done easily with Compass:
Connect to MongoDB from node.js
To connect to MongoDB from node.js we will use
It has to be installed from Apollo Server directory:
cd apollo-server npm install mongoose
To connect to MongoDB we can establish a connection in
src/index.js by adding this code:
Make sure your MongoDB uses port 27017. If not, update the URI. You can find the URI in Compass UI:
Create Book model and query data from MongoDB
mongoose. Create Book model in
We can query all books by simply calling the
To make data accessible through GraphQL, we need to add
Book type to GraphQL schema and
books query that returns an array of
Book elements in
We also need to add the resolver that is querying MongoDB with
Querying with Playground should return an empty array:
Let’s add a book to MongoDB with Compass by clicking
ADD DATA -> Insert document:
Once the book is added it should be returned in
Display books in React UI
To make styling easier, let’s use Twitter Bootstrap.
Add bootstrap CSS to
To display books, we will create two components:
Books- for querying GraphQL API and displaying all books
Book- for displaying a single book (data will be passed from
Let’s start with
Book component. Create new file
Books component will use the
Book component to display all books from MongoDB. It will also fetch data from GraphQL API. We need to define GraphQL query (which we can copy from Playground), and query Apollo Server with
useQuery. Create new file
Books component in
App component and display it on the main page:
When you go back to the website you should see the book, which we added to the database displayed in a table:
Before we start working on creating, deleting and updating books, let’s add navigation to our UI with React Router. This will allow us to separate different views instead of cluttering them on the same page.
First, we need to install
react-router-dom in our React app:
cd web-ui npm install react-router-dom
We need to wrap all components in
BrowserRouter, map routes to components, and add links allowing us to navigate between routes.
All changes that need to be done take place in
After that, you should see the main page with navigation and links to home that displays
Hello component, and a link to books that display the list of books from MongoDB in a table:
Create book mutation
To insert a new book into MongoDB, we need to create a
In GraphQL we can fetch data with
Query, but to create or modify data, we need to create a
Let’s start with updating the schema. Mutations are in a separate block in the schema. We will add
create a mutation that takes
year parameters, and returns
Book object to
Now, we need to implement a resolver for the
create mutation. Similarly to the schema, we need to add a new block
Mutation to our resolvers and
create a function. In the
create function we need to add logic to create a new book and save it to MongoDB. This is
src/resolvers.js after updates:
We can test creating books with GraphQL Playground:
If everything went well you should see a new book in MongoDB. Notice
__v0 field in the book inserted with Mongoose. This is
versionKey property, which is set on each document when first created by Mongoose.
UI for creating books
Let’s start with a new
CreateBook component. We need to create a new form to collect
year, and call the
create mutation we created in the previous section.
We will utilize
useState hook to access
input field data (
To call mutation, we will use
useMutation hook from
We can get
CREATE_BOOK_MUTATION from GraphQL playground.
Make sure to convert year from string to integer when passing it as a variable to mutation. Otherwise, it will return an error. You can simply add
+ before the variable to convert a string to an integer.
src/components/CreateBook.js file to
We will also create a separate route
/create to display the
CreateBook component, and a link to that route. Update
You may notice that a new book does not appear on the books list without a refresh. There are two ways to fix it:
- Update local GraphQL cache - use this approach when performance is a priority over correctness
- Refetch query - use this approach when correctness is more important than performance
We will use the refetch query approach, which is usually the best default answer unless you really care about high performance.
This requires passing a list of queries to refetch to
As you can see, we need
BOOKS_QUERY, which we already defined in
Books component. To do not copy/pasta, let’s extract all GraphQL queries and mutations to
Now you can remove
BOOKS_QUERY variable from
Books component and just import it from
CreateBook component you can remove
CREATE_BOOK_MUTATION, and import both
Delete mutation will be very similar to
create mutation from the previous section. We just need a mutation that takes
id of a book we want to delete.
Let’s update schema in
src/models/typeDefs.js by adding
delete mutation that takes
id parameter and returns the same id if book is successfully deleted:
And implement the resolver function in
We can test deleting book in the GraphQL playground by grabbing an id from MongoDB Compass:
If we try to delete a book that doesn’t exist we should get
null in response:
We have our backend working. Let’s add delete functionality to UI.
Add delete mutation to
graphql.js (you can copy/paste from GraphQL Playground):
We will add a delete button to
Book component, and call
delete mutation from there:
We also need to add a new column to the table header in
Clicking a delete button should delete a book from MongoDB and delete a row from the books list table:
Editing is almost like creating. You need to pass
id of an existing book in addition to
Let’s update the schema by adding
And resolvers, by implementing
Test in GraphQL Playground:
To enable editing from UI, we will add an edit button to
Book component. It will change text to
input fields when in editing mode, and display save and cancel buttons to commit or discard changes.
Let’s start with adding
src/components/Book.js by adding editing state, buttons and call to update book:
Now, you should be able to edit books inline:
To make sure that everything works as expected, you can double-check if books are being properly created, deleted and updated in Mongo with MongoDB Compass.
Congratulations! Now, you know how to build web apps with MARN stack!
You can find the entire code in this GitHub repo: https://github.com/jj09/marn.
I also recorded a video version of this guide:
To learn more about Apollo Server, checkout Apollo docs. It’s pretty good!
To dive into MongoDB check out MongoDB CRUD Operations and mongoose API. Once you learn how to write Mongo queries with MongoDB Shell, you don’t really need to go over mongoose API as it’s almost identical.
You can very easily swap different components for MARN stack. E.g., swap React with Vue.js as I did for vue-apollo demo project.