How to Get Started With React + JSON Server

How to Get Started With React + JSON Server

Building a note taking app with minimal backend
Ferenc AlmasiLast updated 2021 November 11 • Read time 22 min read
Learn how you can use React with JSON server to quickly set up a backend for your application for mocking and prototyping.
  • twitter
  • facebook
React

Building the frontend of an application is often just one part of the whole equation. For an application that needs to manage users, store state, allow CRUD operations, and so on, having a frontend is not enough. You also need the backend counterpart that can communicate with a database, and send responses down to the UI that the frontend can consume.

Of course, as frontend developers, the last thing we want to deal with is setting up a database, a server, the routes, handling HTTP requests, and the list goes on and on. These are just some of the things you need to do to get a backend up and running for your frontend app. JSON Server is an NPM package that aims to provide an easy solution for that.

What is JSON Server?

JSON Server provides a full fake REST API that requires no configuration or coding to set up. With its help, we can quickly and easily set up a backend for prototyping and mocking, without having to worry about the underlying logic. This way, we can focus more on developing the frontend, and less on configuring the backend.

The React app we will build

We will build a note-taking app that will use React as a frontend, and JSON-Server as the backend. We will also integrate Editor.js, a blocked-style editor, similar to WordPress’s Gutenberg. If you would like to get the full project in one go, you can clone it from GitHub. Without further ado, let’s jump into setting up the project first with Snowpack.

Note taking app created with React and JSON server

Table of Contents

  1. Setting up Snowpack
  2. Setting up the JSON Server
    1. How to communicate with the JSON server
  3. Setting up React
    1. Adding Sass Support
  4. Requesting data from the JSON server
    1. Fetching the data with hooks
  5. Displaying the Sidebar
    1. Adding new pages
    2. Sending requests to the JSON server
  6. Displaying the page
    1. Saving pages
    2. Setting up Editor.js
    3. Removing pages
  7. Summary

Setting up Snowpack

For this project, I’m using Snowpack, which is a build tool that uses an unbundled development approach.

What is Snowpack?

To set up Snowpack with React, run the following command, where notlify is the folder you want the project to be bootstrapped:

npx create-snowpack-app notlify --template @snowpack/app-template-minimal

This will create a new NPM project for you with the minimum required files for a Snowpack project. If you cd into the project folder, and run npm run start, you will be greeted with the following message:

Welcome to Snowpack!

Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Setting up the JSON Server

Now that we have a package.json file, let’s quickly set up the JSON Server. To set it up, all we have to do is install it as a dependency and the rest will be handled by the package. Run:

npm i json-server --save-dev

Once the JSON server is installed, open your package.json file, and add a new command for starting the server. This will serve as our backend:

Copied to clipboard!
"scripts": {
  "start": "snowpack dev",
  "start:server": "json-server --watch db.json",
  ...
}
package.json

It will tell the JSON server to start a server and watch db.json for file changes. You will notice that it will create a db.json file at the root of your project directory. We are going to use a different format for the data, however, so change the contents of db.json to the following:

Copied to clipboard!
{
    "pages": [
        {
            "id": "491cee0d-17f9-4629-ac33-0dd8ef985b8a",
            "title": "🏠 Home",
            "content": {
                "blocks": [
                    {
                        "id" : "w2YBRdFkRK",
                        "type" : "paragraph",
                        "data" : {
                            "text" : "Get a full fake REST API"
                        }
                    }
                ]
            },
            "children": []
        }
    ]
}
db.json

We will have a list of pages in an array with an id, a title, and a content for the page that will hold data entirely generated by Editor.js. You can also have an optional children array with the same data structure to allow nested pages.

How to communicate with the JSON server

The server will automatically create routes for the different nodes we have inside db.json. This means that since we have a pages node, we can now access localhost:300/pages, which will respond with all pages. We can also fetch individual pages using an id, or query params. The following will all return the same page:

  • GET /pages/491cee0d-17f9-4629-ac33-0dd8ef985b8a
  • GET /pages?id=491cee0d-17f9-4629-ac33-0dd8ef985b8a
  • GET /pages?title=Home

To update them, we will need to use POST / PUT / DELETE. The only thing we need to keep in mind when working with the JSON server is to set the content type to application/json. Everything else is handled by the server internally.


Setting up React

Now that we have the server out of the way, let’s focus our attention to React. Currently, this project has nothing in common with a React app, so first, add react and react-dom as dependencies to use them:

npm i react react-dom

Then, to make the project structure more scalable, create two new folders called src and public. The core of the app will go inside the src folder and any static assets, such as images will go inside the public folder.

To make the new structure work with Snowpack, update yoursnowpack.config.mjs file with the following:

Copied to clipboard! Playground
export default {
    mount: {
        public: '/',
        src: '/'
    },
    ...
}
snowpack.config.mjs

This will tell Snowpack to build both the public and src folder to the root of the project once it’s deployed. Now you can move your index.html inside the public folder. Make sure you also create an entry for React inside it:

Copied to clipboard!
  <body>
-     <h1>Welcome to Snowpack!</h1>
+     <div id="root"></div>
index.html

Since the HTML references index.js, let’s create an index.jsx inside the src folder. We don’t need to worry about the extension as Snowpack will take care of that.

Copied to clipboard! Playground
import React from 'react'
import ReactDOM from 'react-dom'


ReactDOM.render(<div>👋</div>, document.getElementById('root'));


index.jsx

Adding Sass Support

To make it easier to work with CSS, we can easily add Sass support by adding npm i @snowpack/plugin-sass. All you have to do is include it inside the plugins array in snowpack.config.mjs:

Copied to clipboard!
  plugins: [
+     '@snowpack/plugin-sass'
  ]
snowpack.config.mjs

To keep the tutorial focused on React and JSON server, I won’t go into details about the CSS involved, you can get the full set of styles in the GitHub repository.


Requesting data from the JSON server

To start requesting the data from the server, let’s create the base component for our app that will fire the requests:

Copied to clipboard!
  import React from 'react'
  import ReactDOM from 'react-dom'
+ import App from './app'

- ReactDOM.render(<div>👋</div>, document.getElementById('root'));
+ ReactDOM.render(<App />, document.getElementById('root'));
index.jsx
Create an empty app.jsx next to index.jsx

Since we want to have a global state for our pages, we can use the context API to store everything at the top-most component. Create a new directory called context in your src folder, and add a page.jsx file with the following:

Copied to clipboard! Playground
import { createContext } from 'react'

const PageContext = createContext({});
const PageProvider = PageContext.Provider;

export { PageContext, PageProvider };
page.jsx

This will export both the context and the provider. In order to use the context, we need to import the provider into our app.jsx, and wrap our components with it:

Copied to clipboard! Playground
import React from 'react'

import { PageProvider } from './context/page'

const App = () => {
    const context = ?;

    return (
        <PageProvider value={context}>
            { /* The rest of the components will be placed here */ }
        </PageProvider>
    );
};

export default App;
app.jsx

The context variable will be passed as a value to the PageProvider, but in order to get the correct value for the context, we need to first fetch the data from the JSON server.

Fetching the data with hooks

To keep the component small and clean, I will be using a custom hook for fetching the pages, called useFetch.

How to fetch data with react hooks?

We can point this hook to the /pages endpoint, and display a loading state as long as we are waiting for the response:

Copied to clipboard! Playground
import React, { useState, useEffect } from 'react'

import Sidebar from './components/sidebar'
import Page from './components/page'

import { PageProvider } from './context/page'
import useFetch from './hooks/useFetch'

const App = () => {
    const [data, loading] = useFetch('http://localhost:3000/pages');
    const [pages, setPages] = useState(data);
    const [activePage, setActivePage] = useState(data);

    useEffect(() => {
        if (data) {
            setPages(data);
            setActivePage(data[0]);
        }
    }, [data]);

    if (loading || !data) {
        return <div>loading...</div>
    }

    const context = {
        sidebar: [pages, setPages],
        page: [activePage, setActivePage]
    };

    return (
        <PageProvider value={context}>
            <Sidebar />
            {activePage && <Page />}
        </PageProvider>
    );
};

export default App;
app.jsx

Once we got the pages in the response, we can use it for two different states: one for the sidebar, and one for the currently active page. We can set both of them using a useEffect and useState hooks. Note that the active page will be always set to the very first page initially, that is the first element of data.

We also want to pass these states to the context so we can access them in all child components. This way we will be able to avoid passing down data through deeply nested components, and we can access everything through the use of useContext.


Displaying the Sidebar

As we could see, our app imports two new components now: Sidebar, and Page. Create both of them inside a components folder. At this stage, we have nothing but the markup and the context:

Copied to clipboard! Playground
import React, { useContext } from 'react'
import Navigation from './navigation'

import { PageContext } from '../context/page'

const Sidebar = () => {
    const { sidebar } = useContext(PageContext);
    const [pages, setPages] = sidebar;

    return (
        <aside>
            <div className="logo">
                <img src="img/logo.png" />
                <span>Notlify</span>
            </div>
            <Navigation pages={pages} />
            <button>+ New Page</button>
        </aside>
    );
};

export default Sidebar;
components/sidebar.jsx

Note that we can get both pages and setPages destructured from the context we have defined in app.jsx. We can pass pages down to an extra component called Navigation, whose only purpose is to loop through the pages and display them in the sidebar:

Copied to clipboard! Playground
import React, { useContext } from 'react'

import { PageContext } from '../context/page'

const Navigation = ({ pages }) => {
    const { page } = useContext(PageContext);
    const [activePage, setActivePage] = page;
 
    return (
        <ul>
            {pages?.map(page => (
                <li key={page.id}>
                    <span className={activePage.id === page.id ? 'active' : null}
                        onClick={() => setActivePage(page)}
                    >
                        <img src="img/chevron-empty.png" />
                        {page.title}
                    </span>
                </li>
            ))}
        </ul>
    );
};

export default Navigation;
navigation.jsx

If you would like to see how you can achieve a nested structure, make sure to checkout the GitHub repository.

This component uses the context again, but this time, we need to be aware of the active page so that we can:

  • Highlight the appropriate menu when the user clicks on it.
  • Set the clicked page as the active one.
Highlighting elements in the sidebar
The highlight remains, even after the element loses focus.

Adding new pages

We also want to be able to add new pages, for which we already have the button inside sidebar.jsx. Attach an event listener to it:

Copied to clipboard!
return (
    <aside>
        <div className="logo">
            <img src="img/logo.png" />
            <span>Notlify</span>
        </div>
        <Navigation pages={pages} />
-       <button>+ New Page</button>
+       <button onClick={() => addNewPage()}>+ New Page</button>
    </aside>
);
sidebar.jsx

And let’s add a new function before the return statement with this function’s name:

Copied to clipboard! Playground
import { uuidv4, add } from '../utils'

const addNewPage = () => {
    const page = {
        id: uuidv4(),
        title: '✍️ Untitled masterpiece',
        content: {
            blocks: [],
        },
        children: []
    };
        
    add(page);
    setPages([
        ...pages,
        page
    ]);
};
sidebar.jsx

So what is going on here? First, we want to have the basic object structure for a new page that we define for the page variable. To make sure we get unique ids for each page, I’ve created a utils.js file and turned to StackOverflow to find out how you can generate unique identifiers, and apparently, the following will do it:

Copied to clipboard!
export const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    const r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
});
utils.js

But what happens if we get the same ID for two different pages? Since we are using Math.random, we can run into duplicates, right? We are probably good with this small application.

According to Wikipedia — “the probability to find a duplicate within 103 trillion version-4 UUIDs is one in a billion.”

Sending requests to the JSON server

In the above code example, I’ve also imported an add function. I’ve created three different functions for adding, updating, and removing pages from the mock database. These functions are all using the Fetch API, and you can add them to your utils.js. Make sure you set the correct headers in order for the JSON server to accept your requests:

Copied to clipboard! Playground
const request = ({ endpoint, method, data }) => {
    fetch(endpoint, {
        body: JSON.stringify(data),
        method,
        headers: {
            'Content-Type': 'application/json'
        }
    });
};

export const add = data => {
    request({
        endpoint: 'http://localhost:3000/pages',
        method: 'POST',
        data
    });
};

export const update = page => {
    request({
        endpoint: `http://localhost:3000/pages/${page.id}`,
        method: 'PUT',
        data: page
    });
};

export const remove = page => {
    request({
        endpoint: `http://localhost:3000/pages/${page.id}`,
        method: 'DELETE'
    });
};
utils.js

Using the add function and setPages, we can update both the mock data and the state of the app with our new entry.

Adding new pages to the app
Looking to improve your skills? Check out our interactive course to master React from start to finish.
Master Reactinfo Remove ads

Displaying the page

But there’s still one core part that is missing, and that is, displaying the contents of the page, and make them editable. Create the Page component if you haven’t already, and add the markup we need:

Copied to clipboard! Playground
import React, { useContext } from 'react'

import { PageContext } from '../context/page'

const Page = () => {
    const { page, sidebar } = useContext(PageContext);
    const [activePage, setActivePage] = page;
    const [pages, setPages] = sidebar;

    return (
        <main>
            <div className="controls">
                <input onChange={updateHeading} value={activePage?.title || ""} />

                <img src="img/save.png" onClick={save} />
                {pages[0].id !== activePage.id &&
                    <img src="img/trash.png" onClick={deletePage} />
                }
            </div>
            <div id="editorjs" />
        </main>
    );
};

export default Page;
page.jsx

There are two things we need to note up front. First, we are going to need both the page and the sidebar from the context. This is so that we can update not just the active page, but the sidebar at the same time.

I’ve also wrapped the trash icon into an if statement, to make sure we always have one default page. This prevents the user from deleting the home page. We also have three different functions we need to define:

  • updateHeading: Update the state of the input so it is reflected on the UI.
  • save: Save the page once we click the save button, and update the state, so it reflects the updated state if we revisit the page.
  • delete: Remove the page, also update the state to remove it from the sidebar.

Let’s go in order, and define the updateHeading function, which is going to be the smallest of them:

Copied to clipboard! Playground
const updateHeading = e => {
    setActivePage({
        ...activePage,
        title: e.target.value
    });
};
page.jsx
We simply want to reset the state of the active page with the updated title.

Saving pages

For saving the pages, we have the save function that is triggered once we click the save icon:

Copied to clipboard! Playground
const save = () => {
    const updatePages = pages => pages.map(page => {
        if (page.id === activePage.id) {
            page.title = activePage.title;
        }

        return page;
    });

    setPages(updatePages(pages));
    update(activePage);
};
page.jsx

In order to update the name inside the sidebar, we want to call setPages with the updated pages. To update the correct one, we can simply map over the pages and set the title if the id of the page matches the id of the active page. This, however, will only update the title, and we don’t have any means to update the content, so let’s look into setting up Editor.js.

Updating the page title

Setting up Editor.js

To set up editor.js, we need to add it as a dependency first:

npm i @editorjs/editorjs @editorjs/header @editorjs/list

This will add the core editor.js plugin, as well as two extra block elements. By default, editor.js only comes with a paragraph tag, and everything else needs to be added separately. We can import them to our Page component:

Copied to clipboard! Playground
import EditorJS from '@editorjs/editorjs'
import Header from '@editorjs/header'
import List from '@editorjs/list'

const editorJS = {
    instance: null,

    getInstance(data) {
        if (!this.instance) {
            this.instance = new EditorJS({
                data,
                tools: {
                    header: Header,
                    list: List
                }
            });
        }

        return this.instance;
    }
};

const Page = () => { ... };
page.jsx

I have also set up a singleton above the component, to make sure we only have one single instance of editor.js at any given time. The constructor accepts a configuration object, where we can set the data (that we will set once we get the instance) and the tools that we want to use. Inside the component, we can call the getInstance function to create a new editor:

Copied to clipboard! Playground
const editor = editorJS.getInstance(activePage.content);

// Make sure you import `useEffect` at the top
useEffect(() => {
    if (activePage.content.blocks.length) {
        editor.render?.(activePage.content);
    }

    return () => {
        editor.clear();
    };
}, [activePage]);
page.jsx
We set the data using the content of the active page.

We also want to use a useEffect hook to render the content into the page and clear it once we switch pages. Now we can add new blocks to our pages, but we cannot save the content, because we only update the title inside the save function, so let’s update that:

Copied to clipboard! Playground
const save = async () => {
    const data = await editor.save();
    const updatePages = pages => pages.map(page => {
        if (page.id === activePage.id) {
            page.content = data;
            page.title = activePage.title;
        }

        return page;
    });

    setPages(updatePages(pages));
    update(activePage);
};
page.jsx

Notice that I have changed the function to async, as editor.save returns a promise. This will hold the exact same data structure that we need to assign for the content of the page.

Editing the content of a page

Removing pages

Only one last thing remained, and that is to actually be able to remove pages, which we can do with simple filtering:

Copied to clipboard! Playground
import { update, remove } from '../utils' 

const deletePage = () => {
    const filteredPages = pages.filter(page => page.id !== activePage.id);

    setPages(filteredPages);
    setActivePage(pages[0]);
    remove(activePage);
};
page.jsx

Note that I also set the active page to the home page, so we are not stuck on a page that has been deleted.

Removing pages from the app

Summary

And with that being said, you’ve just learned how to create a React application that is backed by a JSON server. This eliminates the extra time needed for setting up a server that you can interact with, without having to wait for the backend part to be ready.

As mentioned at the beginning of the tutorial, you can get the full source code in one piece from GitHub. Have you worked with a JSON server before? What are your impressions? Let us know your thoughts in the comments below! Thank you for reading through, happy coding!

  • twitter
  • facebook
React
Did you find this page helpful?
📚 More Webtips
Frontend Course Dashboard
Master the Art of Frontend
  • check Access 100+ interactive lessons
  • check Unlimited access to hundreds of tutorials
  • check Prepare for technical interviews
Become a Pro

Courses

Recommended

This site uses cookies We use cookies to understand visitors and create a better experience for you. By clicking on "Accept", you accept its use. To find out more, please see our privacy policy.