Building a blog with React and RestDB
Tags: javascript, webdev, react, restdb
Published: 2022-04-29 21:58:38

by [Origho Precious](https://blog.openreplay.com/authors/origho-precious)


When building apps that require a backend, we spend much time setting up a server and database, writing APIs with which the frontend can send and receive data to and from the backend. With [RestDB](https://restdb.io/), you can easily spin up a server and create a database. Each database collection comes with Rest API endpoints to perform CRUD operations on the database, and of course, you can add more collections. In this article, we will learn how RestDB works by building a blog app with React using RestDB to store the blog's articles. 


## What is RestDB?

[**RestDB**](https://restdb.io/) (also known as restdb.io) is a simple online NoSQL database backend with automatic APIs and low code javascript hooks. Basically, with RestDB you get a *ready-to-use* database with automatic APIs that you can use to perform CRUD operations on the database. RestDB completely takes away the complexity of building a server and writing endpoints for basic CRUD operations. There are many reasons why software developers should consider using RestDB.

- Easy to use: With RestDB, you can define the schema for your data, and add collections and descriptions for your models.
- It is free for teams: You can give access to people on your team, depending on their needs and access levels.
- With RestDB, you don't only get a database. You can also link the database up with your web page domain easily.

They are other NoSQL database backends like RestDB, like [Google Firebase](https://firebase.google.com/products-build), [Amazon DynamoDB](https://aws.amazon.com/dynamodb/), [Azure Cosmos DB](https://docs.microsoft.com/en-us/azure/cosmos-db/introduction), [Amazon DocumentDB](https://aws.amazon.com/documentdb/), and more. These alternatives also provide easy-to-use and fully managed cloud-based databases just like RestDB with some other added features like MongoDB compatibility (Amazon DocumentDB), the option to integrate with an SQL database(Azure Cosmos DB), etc. Learn more about RestDB alternatives here: [5 Powerful Alternatives to REST APIs](https://leapgraph.com/rest-api-alternatives).

Next, we will learn how to work with RestDB by building a blog app with React and connecting it to a RestDB database.


## Building a Blog with React and RestDB.

To get started, we will clone a [repo](https://github.com/Origho-precious/RestDB-blog-starter) from Github. This repo contains the project's starting files (components) and some config/setups. To do this, open your command line terminal and run the command below.

```
git clone https://github.com/Origho-precious/RestDB-blog-starter.git
```

After cloning, navigate into the project folder and install dependencies by running `yarn install` or `npm install`. In this project, I have already added some components and configs. We have [tailwind](https://tailwindcss.com/) setup for styling, [react-router-dom](https://reactrouter.com/web/guides/quick-start) for navigation, to render markdown styles we will be using [react-markdown](https://github.com/remarkjs/react-markdown) and two of its plugins: [remark-gfm](https://github.com/remarkjs/remark-gfm) and [rehype-raw](https://github.com/rehypejs/rehype-raw). Let's quickly go over the components we have in the project.

- `Button` - This is a simple Button component with two variants.
- `Navbar` - This component shows the blog's name and a button that routes a page where we can create a post and a button to go back to the home page.
- `PreviewPost` - This component renders the body of a post with `react-markdown`. We will use this in a *write* page to preview what the article's body looks like.

We also have three helper functions in src/utils/helperFunctions.js,

- `truncateText` will be used for truncating texts so they don't overflow out of their parent elements.
- `calcReadTime` calculates the read time of an article, using the number of words contained in the body of the article and a metric of 200 words per minute.
- `convertDate` converts timestamps to a particular data format using `moment`.

Lastly, in `src/page`, there are three folders with `index.jsx` files. The files in each just render paragraphs for now, but we will update them later in this article.

Next, let's create a RestDB account, set up a database, and connect our app to it.

## Creating a RestDB Account 

Let's navigate to the [RestDB official webpage](https://restdb.io/).

![RestDB landing page](https://blog.openreplay.com/static/724d5bbedda2b1be08fa5db1d804500b/00206/image01.png)

Click on the "Get your account" button. That will take you to a page where you can signup.

![Signup page](https://blog.openreplay.com/static/db1ee40804b42992b18aa3db07dcd15e/68382/image02.png)

After signing up and verifying your account (if you don't use the Google or Facebook signup options), You will see a page like the one below.

![Databases page](https://blog.openreplay.com/static/8e448f0825f2baff5d0e16ebe2d0daba/68382/image03.png)

Yay!! You have now created your RestDB account. Next, let us create a database for our blog app. To do this, click on the "Create New" button at the top-right of the page. It will pull up a modal like the one below, where we can enter the name of the database or even copy an existing database. But we will be creating a new one here. So enter what you'd like to name the database; I will name mine `restblog`.

![Create a Database](https://blog.openreplay.com/static/e40e802550427bc618a7d7ef33c4e071/68382/image04.png)

We just created our first RestDB database. You should see a screen like this.

![Our first RestDB database](https://blog.openreplay.com/static/93d48823494991859de32fa57d519c8b/68382/image05.png)
 
Click on the database name to create a collection, define a schema for the collection's models and connect our blog with it.

You will see a page like the one below, basically empty except for the sidebar and an icon at the top-right of the page. Click on that gear icon. It will enable developer mode so we can do all the things stated in the paragraph above.

![Dashboard](https://blog.openreplay.com/static/5cee50bd6723601b59379700214712ab/c22b4/image07.png)

When you click on that icon, you should see a screen like this,

![Developer mode](https://blog.openreplay.com/static/3cc88ab3f435d5189928d5142f56eb09/3076a/image08.png)

I know that doesn't look very clear, and you might want to ask why we have those things there when we've not created any collection in the database. When you create a database in RestDB, it auto-generates a "users" collection, and it also generates other collections for emails, etc. For this article, we won't be handling authentication. Let's simply add a collection for our blog's articles.

To do this, click on the "Add Collection" button. I'll call the collection `articles`. Add a description as well (it's optional, though) and save.

![Adding Collection](https://blog.openreplay.com/static/ed9d69d4484ba7b9145d2fff6dcc57b3/79869/image09.png)

Upon saving, You will see that the collection we just created is in the list of collections. Click on it to add models to it. You should see a page like this.

![Adding models](https://blog.openreplay.com/static/8f779c2be68db255c9104bf32c564619/79869/image10.png)

Here we will be adding fields (models for the collection we created). Click on the "Add Field" button to get started. 

![Adding fields](https://blog.openreplay.com/static/09ee2addb09de2abf17906c19207b717/4f4a0/image11.png)

You will see a screen like the one above. We will be creating four fields:
- **title** - This will be an article’s title
- **body** - The article’s content
- **tags** - Technologies used in the article e.g. react, react-router-dom, etc. it will be optional.
- **timestamp** - time the article was posted. 

Here is a [Github gist](https://gist.github.com/Origho-precious/0acbea1e3f5aa56faae9bea51032e2be) containing what we need to create each field.

If you added the fields following the table above, you should see something like this.

![Complete table](https://blog.openreplay.com/static/0419608a46158b76cc9fe999648b7be5/79869/image12.png)

We have now successfully created a RestDB account, created a database, a collection in that database for the blog's articles. We also add models to the collection. What we have to do next is connect our React app with this database, so we can add, delete, or edit articles in the database.

Let's see the endpoints we can *hit.* RestDB provides a **Swagger** documentation for each database created. To access this documentation, click on the **name** of the database on the top of the sidebar, just beneath the search input. It will navigate us to a page like this.

![Swagger page](https://blog.openreplay.com/static/3100699fe6cd6847f6f3aa5436601f0b/a677d/image13.png)

Click on "API tools" to open up a page like this.

![API Tools](https://blog.openreplay.com/static/f9eb559becc40a66f2d6ea4d0a251980/26968/image14.png)

Click on the link under "Generate Swagger REST API" which will open up the swagger docs. You will see the endpoints autogenerated for the collections in the Swagger docs.

![Autogenerated endpoints](https://blog.openreplay.com/static/fd1767fa98572fd35c111d0207a3afa8/4eb26/image15.png)

We will need an API key to be able to access the database from our app. Go back to the Swagger docs link and click on "API Keys", and you will see this.

![API key for Swagger](https://blog.openreplay.com/static/4df8f74e0abb5484ab724163c9544c28/79869/image16.png)

Click on "Manage API Keys". You will be routed to a new page that should look like the one below.

![Managing API keys](https://blog.openreplay.com/static/d7c769eb9b32035b2728f00d674d8ee6/b71f8/image17.png)

Let's add a new API key for our project by clicking on the "Add new" button. A modal will pop up. 

![Adding the new API key](https://blog.openreplay.com/static/28de01239531a3208232f62e1c5713a8/79869/image18.png)

Add a description and select the type of HTTP request we want this API key to support. As seen above, I selected GET, POST, DELETE, and PATCH. Save and copy the API key generated for you. You can see mine below.

![Saving the API key](https://blog.openreplay.com/static/0e85298ffcbd52762996a8a53a7e63ea/b58f7/image19.png)

Copy the API key, and go to the project you cloned. In the root directory, create a `.env` file. In it, add the API key in this format,

```
REACT_APP_API_KEY=${YOUR_API_KEY}
```

**NOTE: Do not use my API Key because I will have deleted it by the time you read this. 😂** 

With the API key, we can now go back to our code editor and finish up the app.

## Creating an Axios instance with custom configs

Let's create an `axios` instance with some configs. To do this, we will need to add the base URL of the endpoints we will hit, we'd also add some headers (an example: our API key) so we don't have to add them on all requests. Let's get started by installing `axios` with `yarn add axios` or `npm install axios`. With Axios installed, let's navigate to `src/utils` and add a new file, `api.client.js`, with the code below.

```javascript
import axios from "axios";

export default axios.create({
  baseURL: "https://restblog-dced.restdb.io/rest",
  headers: {
    "Content-Type": "application/json",
    "x-apikey": process.env.REACT_APP_API_KEY,
    "cache-control": "no-cache",
  },
});
```

We created an `axios` instance with a base URL and custom headers. With it, we will make all the HTTP requests we will need in this app.


## Building a PostForm component

In this section, we will build a component with which, We can create an article and make a POST request to add it to our RestDB database. We will also be able to edit an article with this component. In the `components` folder inside `src`, create a new folder called `PostForm` and a file inside it `PostForm.jsx` and add the code below.

```javascript
import { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import axios from "../../utils/api.client";

const PostForm = ({ state, id, setArticleBody }) => {
  const history = useHistory();
  const [data, setData] = useState(null);
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");
  const [tags, setTags] = useState("");
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    if (id) {
      const fetchArticles = async () => {
        setLoading(true);
        try {
          const { data } = await axios.get(`/articles/${id}`);
          setData(data);
          setTitle(data?.title);
          setBody(data?.body);
          setArticleBody(data?.body);
          setTags(data?.tags);
          setLoading(false);
        } catch (error) {
          console.log(error);
          setLoading(false);
        }
      };
      fetchArticles();
    }
  }, [id, setArticleBody]);

  const postArticle = async () => {
    if ((title, body)) {
      setLoading(true);
      setError("");
      try {
        await axios.post(
          "/articles",
          JSON.stringify({
            title,
            body,
            tags,
            timestamp: new Date().toISOString(),
          })
        );
        return history.push("/");
      } catch (error) {
        setLoading(false);
        setError("Something went wrong!");
        return console.log(error);
      }
    }
    setError("Title and Body fields can't be empty!");
  };

  const editArticle = async () => {
    if ((title, body)) {
      setLoading(true);
      setError("");
      try {
        await axios.patch(
          `/articles/${id}`,
          JSON.stringify({
            ...data,
            title,
            body,
            tags,
          })
        );
        return history.push("/");
      } catch (error) {
        setLoading(false);
        setError("Something went wrong!");
        return console.log(error);
      }
    }
    setError("Title and Body fields can't be empty!");
  };

  const onSubmitHandler = (e) => {
    e.preventDefault();
    if (state !== "edit") {
      return postArticle();
    }
    return editArticle();
  };

  ...
```

Above, we imported several dependencies, and then we created states for input and text area values using `useState`. We also initialized the `useHistory` hook to get the `history` object.

This component has three props: 

- `state` - this will either be 'add' or 'edit'. We will know if we're to edit an article or create a new one with this.
- `id` - This will be `null` if the state equals' edit'. If the id isn't null/undefined, we use it to fetch the details of the article we want to edit.
- `setArticleBody` - this function will send the content of the article's body to the page where this component will be used so that the `PreviewPost` component can use it.

We added a `useEffect`, and inside it we are using the `id` prop to fetch the details of the article we want to edit by making an HTTP request with the `id`. We then set the values to their respective states to populate the inputs and text area with them.

Next, is a function `postArticle`. In this function, we check if the `title` and `body`  states have values. If not, we trigger an error as those fields are required to create an article. Otherwise, we make a POST request to the RestDB server sending a stringified object containing the article's `title`, `body`, `tags`(if any), and `timestamp`: all the fields we created in the database. The `timestamp` is set to the article's creation time, converted to [ISO](https://en.wikipedia.org/wiki/ISO_8601) format. Beneath that is a function for editing an article, similar to the `postArticle` function, except that it makes a PATCH request to the server with the article's `id`. 

The `onSubmitHandler` function, is passed to a form and it calls either `editArticle` or `postArticle` function depending on the `state`. 

Let's finish this component with the code below to render some `jsx` styled with `tailwind`.

```javascript
  ...

  return (
    <form
      onSubmit={!loading ? onSubmitHandler : () => {}}
      id="post-article"
      className="w-full"
    >
      <h2 className="mb-6 text-2xl font-bold text-center">
        {state === "add" ? "Add New Blog Post" : "Edit Mode"}
      </h2>
      <div className="w-full">
        <input
          id="title"
          type="text"
          value={title}
          onChange={(e) => {
            setError("");
            setTitle(e.target.value);
          }}
          placeholder="Enter the article's title"
          disabled={id && loading && true}
        />
      </div>
      <div className="w-full my-6">
        <input
          id="tags"
          type="text"
          value={tags}
          onChange={(e) => setTags(e.target.value.trim())}
          placeholder="(Optional) Tags e.g javascript, typescript "
          disabled={id && loading && true}
        />
      </div>
      <div className="w-full">
        <textarea
          id="body"
          onChange={(e) => {
            setError("");
            setBody(e.target.value);
            setArticleBody(e.target.value);
          }}
          value={body}
          placeholder="Write post content. You can use markdown syntax here"
          disabled={id && loading && true}
        />
      </div>
      {error && <p className="text-red-600 text-xs mt-3 -mb-1">{error}</p>}
    </form>
  );
};

export default PostForm;
```    

## Building an ArticleCard component

Let's build a component that will display details of an article, handle delete functionality and route a user to the edit article page. In `src/components`, create a new folder `ArticleCard` and a file named `ArticleCard.jsx` inside it add the code below into the file.

```javascript
import { Link, useHistory } from "react-router-dom";
import {
  calcReadTime,
  convertDate,
  truncateText,
} from "../../utils/helperFunctions";
import axios from "../../utils/api.client";

const ArticleCard = ({ id, title, body, timeStamp, tags, refresh }) => {
  const history = useHistory();
  const handleDelete = async () => {
    const confirmed = window.confirm(
      "Are you sure you want to delete this article?"
    );
    if (confirmed) {
      try {
        await axios.delete(`/articles/${id}`);
        refresh && refresh();
      } catch (error) {
        console.log(error);
      }
    }
  };

  return (
    <Link to={`/article/${id}`}>
      <div
        title={title}
        className="flex flex-col justify-between bg-black h-48 py-10 px-12 rounded-md hover:bg-gray-900 transition-all duration-700 relative"
      >
        <div className="absolute top-4 right-6 flex justify-end">
          <span
            className="mr-5 hover:text-white"
            onClick={(e) => {
              e.preventDefault();
              handleDelete();
            }}
            role="button"
          >
            <i className="fas fa-trash" />
          </span>
          <span
            className="hover:text-white"
            onClick={(e) => {
              e.preventDefault();
              history.push(`/write/${id}`);
            }}
            role="button"
          >
            <i className="fas fa-pencil-alt" />
          </span>
        </div>
        <div>
          <h3 className="font-bold text-2xl mb-4">{truncateText(title, 37)}</h3>
          <div className="flex">
            {tags?.map((tag, idx) => (
              <p key={tag + idx} className="mr-4 opacity-80 text-white text-sm">
                #{tag.trim()}
              </p>
            ))}
          </div>
        </div>
        <div className="flex items-center justify-between">
          <p>{convertDate(timeStamp)}</p>
          <p>{calcReadTime(body)}</p>
        </div>
      </div>
    </Link>
  );
};

export default ArticleCard;
```    

The component above has five props;

- `id` - the id of the article being rendered, needed for deleting and editing the article
- `title`, `body`, `timeStamp`, `tags` - properties of the article. 
- `refresh` - a function that will be called whenever an article is deleted to refresh the page, thereby fetching the updated list of articles from the server.

We have a function that sends a DELETE request to the server with the `id` to delete the article. We render the article's details, truncating the title with `truncateText`, and rendering the read time and date with `calcReadTime` and `convertDate` respectively.

Next, we will update the `write` page component.


## Creating Articles from the App

In this section, we will update the `write` page, so it can handle creating articles from the app with the `PostForm` component we created earlier. This component will also handle article editing using the same `PostForm` component, expecting an `id` param.
Navigate to `src/pages/write/index.jsx` and replace what is there with the code below.

```javascript
import { useState } from "react";
import { useParams } from "react-router-dom";
import Button from "../../components/Button/Button";
import PostForm from "../../components/PostForm/PostForm";
import PreviewPost from "../../components/PreviewPost/PreviewPost";

const Write = () => {
  const { id } = useParams();
  const [previewMode, setPreviewMode] = useState(false);
  const [articleBody, setArticleBody] = useState("");

  return (
    <div
      className="px-20 py-8 relative text-white bg-black w-3/5 mx-auto rounded-lg"
      style={{ height: "85vh", maxHeight: "600px", overflowY: "scroll" }}
    >
      <div
        role="button"
        onClick={() => setPreviewMode(!previewMode)}
        className="absolute right-8 top-6 hover:text-opacity-50 flex items-center duration-500 rdb-preview"
        style={{ color: previewMode ? "#2eff7b" : "" }}
      >
        {previewMode ? (
          <p className="mr-3">Write</p>
        ) : (
          <p className="mr-3">Preview Body</p>
        )}
        {!previewMode ? (
          <i className="fas fa-eye" />
        ) : (
          <i className="fas fa-pencil-alt" />
        )}
      </div>
      <div style={{ display: !previewMode ? "block" : "none" }}>
        <PostForm
          setArticleBody={setArticleBody}
          id={id}
          state={id ? "edit" : "add"}
        />
        <footer className="mt-4">
          <Button form="post-article" type="submit" className="mr-6">
            Publish
          </Button>
        </footer>
      </div>
      <div style={{ display: previewMode ? "block" : "none" }}>
        <PreviewPost children={articleBody} />
      </div>
    </div>
  );
};

export default Write;
```
    

Firstly, we imported the hooks and components we will need in this file. 
`useParams` will help us access particular segments of the page's URL. In this case, we are expecting the special segment of the URL to be an `id` (check `src/App.js`).

We destructured the `id` from the `useParams` hook in the component. We have two states. One will be handling `previewMode`: we will use this state to toggle between showing the `PostForm` or `PreviewPost` components. The second state, `articleBody`, will hold the article's content, set from `PostForm`. We then render the content of the component.


## Fetching and Rendering Articles

In this section, we will update `src/pages/home/index.jsx` file fetch articles from the server and render them using the `ArticleCard` component. To do this we will need the `ArticleCard` component, our Axios instance, `useState` to hold the response of the HTTP request (an array of articles) and another state for refreshing the page. Finally we have a `useEffect` to fetch the articles when the page is rendered or refreshed. Let's do this by replacing the file's content with the code below.

```javascript
import { useEffect, useState } from "react";
import ArticleCard from "../../components/ArticleCard/ArticleCard";
import axios from "../../utils/api.client";

const Home = () => {
  const [articles, setArticles] = useState([]);
  const [loading, setLoading] = useState(false);
  const [refresh, setRefresh] = useState(false);

  const fetchArticles = async () => {
    setLoading(true);
    try {
      const res = await axios.get("/articles");
      setArticles(res?.data);
      setLoading(false);
    } catch (error) {
      console.log(error);
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchArticles();
  }, [refresh]);

  return (
    <section className="w-1/2 mx-auto">
      {loading ? (
        <p className="text-center">Loading...</p>
      ) : articles?.length ? (
        articles?.map((article) => (
          <article key={article?._id} className="mb-4">
            <ArticleCard
              id={article?._id}
              title={article?.title}
              tags={article?.tags?.split(",")}
              body={article?.body}
              timeStamp={article?.timestamp}
              refresh={() => setRefresh(!refresh)}
            />
          </article>
        ))
      ) : (
        <p className="text-center">No article, create post</p>
      )}
    </section>
  );
};

export default Home;
```    

So far, we have written some codes to create, edit and delete articles. One last thing to do is create a page to view the entire content of an article. The `ArticleCard` doesn't show the body/content of an article. Let's do that in the next section.


## Building article page

Navigate to `src/pages/article/index.jsx` and replace the file's content with the code below.

```javascript
import { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router";
import ReactMarkdown from "react-markdown";
import gfm from "remark-gfm";
import rehypeRaw from "rehype-raw";
import axios from "../../utils/api.client";
import { calcReadTime, convertDate } from "../../utils/helperFunctions";

const Article = () => {
  const params = useParams();
  const history = useHistory();
  const [loading, setLoading] = useState(false);
  const [article, setArticle] = useState(null);

  useEffect(() => {
    if (!params?.id) {
      history.push("/");
    }
  }, [history, params]);

  useEffect(() => {
    const fetchArticle = async () => {
      if (params?.id) {
        try {
          setLoading(true);
          const res = await axios.get(`/articles`, {
            params: {
              q: {
                _id: params?.id,
              },
            },
          });
          setArticle(res?.data[0]);
          setLoading(false);
        } catch (error) {
          setLoading(false);
        }
      }
    };
    fetchArticle();
  }, [params]);

  return (
    <div className="w-4/5 mx-auto mt-16 mb-24">
      {loading ? (
        <p className="text-center">Loading...</p>
      ) : article ? (
        <>
          <header className="rounded-md bg-black mb-10 max-w-9/12 py-12 px-20">
            <h1 className="text-2xl text-center font-semibold uppercase">
              {article?.title}
            </h1>
            <div className="flex items-center justify-center">
              <p className="mt-4 text-sm text-center mr-8">
                {convertDate(article?.timeStamp)}
              </p>
              <p className="mt-4 text-sm text-center">
                {calcReadTime(article?.body)}
              </p>
            </div>
          </header>
          <>
            <ReactMarkdown
              className="prose"
              remarkPlugins={[gfm]}
              rehypePlugins={[rehypeRaw]}
              children={article?.body}
            />
          </>
        </>
      ) : (
        <h3>Article not found!</h3>
      )}
    </div>
  );
};

export default Article;
```    

Above, we added code to:

- fetch details of an article using the article's id (gotten from the URL of the page), 
- render the `title`, `tags`, and the `timestamp` of the article properly using the necessary helper functions and render the `body` with `react-markdown` as it might contain markdown syntax.

It is time to test the app. In your terminal, run `yarn start` or `npm run start` to start the dev server. You should see a screen like this.

![The final result](https://blog.openreplay.com/static/cb48372a8f6bdb49f29a56d05e8b099f/68382/image20.png)

You can create, view, edit, and delete posts. Feel free to tweak the code to your preference.

## Conclusions

We've now wholly built a blog app using React and RestDB, an online NoSQL database. In this article, we learned what RestDB is and how to use it by creating a RestDB account, setting up a database, adding a collection to the database, adding models, and defining a schema for them. We also learned how to generate an API key and how to generate a Swagger documentation for a RestDB database server. And finally, we were able to connect our blog app with it.

There are still some more things you can learn about RestDB, handle authentication, add custom routes, use webhooks, and host pages, etc. To learn more, visit the official [documentation](https://restdb.io/docs/).

## Resources
- [Github Repository for project starting code](https://github.com/Origho-precious/RestDB-blog-starter) 
- [Final Project Code Repository](https://github.com/Origho-precious/RestDB-React-Blog)
- [RestDB official documentation.](https://restdb.io/docs/)
- [Tailwindcss docs](https://tailwindcss.com/)
- [React-router-dom docs](https://reactrouter.com/web/guides/quick-start)