How to build a blog app with MongoDB, NextJS, and Buildable
Tags: javascript, tutorial, nextjs, mongodb
Published: 2022-09-16 21:05:36

In this tutorial, we'll build a blog application that allows users to create new blog posts and list all posts on a homepage. The purpose of this post is to show you how to leverage Buildable and MongoDB to build your blog. We'll dive into how easy it is to generate API endpoints in **Buildable** and explore your **MongoDB Atlas** within Buildable's IDE.

Here's a sneak peek of the blog homepage we'll create together.


![Blog home page](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/egnzkdu6z0fu0ypvx3yy.png)


## Prerequisites

For this tutorial, you'll need the following:

- NextJS app - To create the frontend of our blog application
- MongoDB Atlas - To manage our database
- Buildable Account - To create REST endpoints (Create your free account [here](https://app.buildable.dev/sign-in))
- Chakra UI (Optional) - To style our app

## Step 1: Bootstrapping a NextJS app

To bootstrap a NextJS app, you'd need to run this command in your terminal window:

`npx create-next-app@latest`

You'll be prompted to give your app a name. Let's call it "**blog-app."** Once this installation is complete, navigate to this directory by running `cd blog-app`.

At this point, go ahead to install and set up the styling or component library of your choice. For this tutorial, we'll be using Chakra UI.

Now, let's start up our local development server. To do this, we'll run `yarn dev`.

Navigate to `localhost:3000` and you should see this


![NextJS getting started](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d5c39s8ftskeudxenshh.png)

## Step 2: Connecting your MongoDB Atlas

Now that we have installed our NextJS app, we need to connect our MongoDB database to Buildable. To do this, we can either connect an existing MongoDB Atlas Instance or spin up a new MongoDB Atlas instance (coming soon). For now, we'll be connecting an existing instance.

In your Buildable app, navigate to **Databases** and click on **New**. Pick **MongoDB** as the datasource and select **Connect Existing**.

Add in your credentials and connect. 


![Connecting MongoDB](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dvrbhjjyj46g9aw4rq80.gif)


> 💡 If you get stuck on this step, go ahead to check our [docs](https://docs.buildable.dev/connections/action-catalog/mongodb) for more details



Thanks to Buildable, we can create a new collection directly in our IDE. Click MongoDB and create a new collection. Let’s call it **blogs**.

![Create new collection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/x2lozkkuxkak3xivacbh.png)

## Step 3: Defining the blog post schema

With the database connected with Buildable, it is time to create the schema for a single blog post. We'll create a basic schema that contains the following:

- title
- image
- tags
- content
- author

```jsx
{
    _id: '1',
    title: 'How to write a great blog post',
    image:
      'https://images.unsplash.com/photo-1524492449090-a4e289316d9c?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1694&q=80',
    tags: ["community", "webdev"],
		content: "Lorem ipsum is a pseudo-Latin text used in web design, typography, layout, and printing in place of English to emphasize design elements over content. It's also called placeholder (or filler) text."
    author: "Esther Agbaje"
  },
```

## Step 4: Creating the POST endpoint for new blog posts

Now that we have completed the schema of our blog posts, we need API endpoints to insert new posts into our database. So, let's create a POST endpoint to achieve that.

In the Buildable IDE, click **REST** and create a new folder called **blog.**

Next, to create an endpoint, click **Create new** **flow** and give it a name, **new-post**.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a7b9tzwmshp182zfz8h8.gif)


> 💡 It's important to note that the folder structure or naming affects the URL. We'll be organizing all endpoints in this "blog" folder.


You'd notice this workflow provides a POST endpoint to create new posts and send the data to our MongoDB Atlas.


![Post endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3it9w9whsbatahjehrbq.png)

Next, we need a way to insert data via the API into our MongoDB database. Buildable provides a list of action templates for MongoDB.

Click **Add action**, and select **MongoDB** from the list. Now choose the **Insert Document** template.


![Add action](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hk6j5haoivcvsy5r81ge.png)

Navigate to the **Body** tab and put some dummy data in the request body as follows:

```jsx
{
  "title": "Testing new blog",
  "image": "https://images.unsplash.com/photo-1661956602944-249bcd04b63f?ixlib=rb-1.2.1&ixid=MnwxMjA3fDF8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1770&q=80",
  "tag": "community",
  "content": "Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source.",
  "author": "Esther Agbaje"
}
```

Next, go to the **Actions** tab. In the action templates, we need to make a couple of edits to the code.

Remember, the collection is called **blogs** so we adjust this.


> 💡 `$body` represents the entire payload data.


To access the key values dynamically, we use

```jsx
		title: $body.title,
    tag: $body.tag,
    image: $body.image,
    content: $body.content,
    author: $body.author
```

Now, take out unused code and save the workflow. 

Lastly, in the **Response** tab, we need to modify the response on success. Copy the action and paste into the response. Save changes in the response tab. 



![Actions for post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1jotbdhsqfchqxw6uu9d.gif)

Now, run this workflow. Excellent!

Grab the POST endpoint, as we'd be using shortly in our NextJS app.

## Step 5: Building out the User Interface (UI) to create a new blog post

A blog isn't very useful without posts. We need to be able to add new posts via the API we just created.

Back in our Next JS app, in the pages directory, create a new page called `create-post.jsx`.

Here's what we're working towards


![Add post](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qnjc588l7ibtqkuqyaom.png)

From our schema, we need to be able to send the following fields from a form:

- Title
- Image URL
- Content
- Tag
- Author

Here's the snippet of the code for you to easily follow along:

```jsx
import {
  Box,
  Button,
  Center,
  Container,
  FormControl,
  FormLabel,
  Heading,
  HStack,
  Input,
  Select,
  Stack,
  Text,
  Textarea,
} from '@chakra-ui/react';

import React from 'react';

const CreatePost = () => {
  return (
    <Box>
      <Stack spacing='2' p='4' bg='black' color='white' textAlign='center'>
        <Heading>Create a New Blog post</Heading>
        <Text>Fill in the details to create a new blog post</Text>
      </Stack>
      <Box bg='gray.50' h='100vh'>
        <Container
          bg='white'
          maxW='2xl'
          py='20'
          px='10'
          borderRadius={{ base: 'none', sm: 'xl' }}
        >
          <form>
            <Stack spacing='6'>
              <FormControl>
                <HStack>
                  <FormLabel htmlFor='title'>Title</FormLabel>
                  <Input boxShadow='sm' id='title' type='text' name='title' />
                </HStack>
              </FormControl>
              <FormControl>
                <HStack>
                  <FormLabel htmlFor='image'>Image URL</FormLabel>
                  <Input boxShadow='sm' id='image' type='text' name='image' />
                </HStack>
              </FormControl>
              <FormControl boxShadow='sm'>
                <FormLabel htmlFor='content'>Content</FormLabel>
                <Textarea id='content' name='content' />
              </FormControl>
              <FormControl boxShadow='sm'>
                <FormLabel htmlFor='tag'>Choose tag</FormLabel>
                <Select name='tag' id='tag'>
                  <option value='community'>Community</option>
                  <option value='web-development'>Web Development</option>
                  <option value='automation'>Automation</option>
                </Select>
              </FormControl>
              <FormControl boxShadow='sm'>
                <FormLabel htmlFor='author'>Author</FormLabel>
                <Select name='author' id='author'>
                  <option value='Esther Agbaje'>Esther Agbaje</option>
                  <option value='Tom Jones'>Tom Jones</option>
                  <option value='Sara Nze'>Sara Nze</option>
                </Select>
              </FormControl>
            </Stack>
            <Center>
              <Button
                boxShadow='sm'
                size='lg'
                mt='12'
                colorScheme='blue'
                type='submit'
              >
                Create post
              </Button>
            </Center>
          </form>
        </Container>
      </Box>
    </Box>
  );
};

export default CreatePost;
```

Now, we need to be able to post data via our API to our database.

We create a function called `handleSubmit` to post our data.

```jsx
const handleSubmit = () => {
    const formEl = e.currentTarget;
    e.preventDefault();
 toast({
      title: 'New Post created.',
      status: 'success',
      duration: 9000,
      isClosable: true,
    });
    const formData = new FormData(formEl);
    const data = Object.fromEntries(formData);
    fetch(
      'INSERT POST ENDPOINT HERE',
      {
        method: 'POST',
        body: JSON.stringify(data),
        headers: {
          'Content-type': 'application/json; charset=UTF-8',
        },
      }
    )
      .then((res) => {
        res.json();
        formEl?.reset();
      })
      .catch((err) => {
        console.log(err.message);
      });
  };
```

> Remember to switch out 'INSERT POST ENDPOINT HERE' with your actual POST endpoint.


Now, we need to connect the handleSubmit function to the `onSubmit` event in our form by adding `onSubmit={handleSubmit}`

Finally, create a post and see what happens. Your post is successfully created, but of course, you don't see it. Why? We can only create new posts with the POST endpoint. 

We need to set up a GET endpoint that fetches all the posts. Let's do that in the next step.

## Step 6: Creating a GET endpoint to fetch all blog posts

Go to the Buildable IDE and create a new flow within the blog folder. We'll call it **get-posts.** 

Set the flow method to **GET**.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqay57g8ft3o0glzyuvb.gif)

Click **Add action** button. You should see a list of templates. First select MongoDB as the platform and choose the **List Data** template.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/00skm1879x60rl8kvp1a.png)

Now, let's edit the template accordingly. The collection is blogs, and we need the following fields:

- title
- image
- content
- tag
- author


![List Data](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6o5gzz6fnuqzgxwif0nj.png)

With that completed, copy the actions, navigate to the response tab and paste it in. Save the changes in the response tab.


![Add action to node](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c89zba82il1y2r2s134o.gif)

Next, save the entire workflow and run it. Now copy the GET endpoint. We'd be using it in our NextJS app.

## Step7: Building the UI that shows all blogs

Back in our NextJS app, open up `index.jsx` located in the `pages` directory. Let's clean it up and add the components we need.

```jsx
import { Box, Heading, Input, InputGroup, Stack, Text } from '@chakra-ui/react';

const Home = () => {
  return (
    <Box>
      <Stack
        bg='black'
        color='white'
        spacing={{ base: '8', md: '10' }}
        align='center'
        p='8'
      >
        <Stack spacing={{ base: '4', md: '6' }} textAlign='center'>
          <Stack spacing='4'>
            <Text
              fontWeight='semibold'
              color='blue.50'
              fontSize={{ base: 'sm', md: 'md' }}
            >
              Our Blog
            </Text>
            <Heading size='xl'>Latest blog posts</Heading>
          </Stack>
        </Stack>
        <InputGroup size='lg' maxW={{ md: 'sm' }}>
          <Input placeholder='Search' variant='filled' colorScheme='blue' />
        </InputGroup>
      </Stack>
    </Box>
  );
};

export default Home;
```

Now, let's fetch the blogposts from the database using NextJS' **GetStaticProps**.

```jsx
export async function getStaticProps() {
  const res = await fetch(
    'INSERT GET ENDPOINT HERE'
  );
  const result = await res.json();
  const posts = result.rows;

  return {
    props: {
      posts,
    },
  };
}
```

> Remember to switch out 'INSERT GET ENDPOINT HERE' with your actual POST endpoint.


P.S I'm getting the image URLs from unsplash.com, so we need to add the domain in the `next.config.js` file.

Your `next.config.js` should look like this:

```jsx
/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  swcMinify: true,
  images: {
    domains: ['images.unsplash.com'],
  },
};

module.exports = nextConfig;
```

Finally, let's render the fetched post in the UI, the final code should be

```jsx
import {
  Badge,
  Box,
  Container,
  Heading,
  HStack,
  Input,
  InputGroup,
  Link,
  SimpleGrid,
  Stack,
  Text,
} from '@chakra-ui/react';
import Image from 'next/image';

const Home = ({ posts }) => {
  return (
    <Box>
      <Stack
        bg='black'
        color='white'
        spacing={{ base: '8', md: '10' }}
        align='center'
        p='8'
      >
        <Stack spacing={{ base: '4', md: '6' }} textAlign='center'>
          <Stack spacing='4'>
            <Text
              fontWeight='semibold'
              color='blue.50'
              fontSize={{ base: 'sm', md: 'md' }}
            >
              Our Blog
            </Text>
            <Heading size='xl'>Latest blog posts</Heading>
          </Stack>
        </Stack>
        <InputGroup size='lg' maxW={{ md: 'sm' }}>
          <Input placeholder='Search' variant='filled' colorScheme='blue' />
        </InputGroup>
      </Stack>

      <Box bg='gray.50' minH='900px'>
        <Container pt='20' maxW='6xl'>
          <SimpleGrid spacing={12} margin='auto' columns={3} pb='8'>
            {posts.map((post) => (
              <Link key={post._id} _hover={{ textDecor: 'none' }} role='group'>
                <Stack boxShadow='base'>
                  {post.image && (
                    <Box overflow='hidden'>
                      <Image
                        src={post.image}
                        alt={post.title}
                        width={400}
                        height={300}
                        objectFit='cover'
                      />
                    </Box>
                  )}
                  <Stack spacing='2' p='6'>
                    <Heading size='md'>{post.title}</Heading>
                    <Text noOfLines={3}>{post.content}</Text>

                    <HStack spacing='8'>
                      <Text as='i' fontSize='sm'>
                        by {post.author}
                      </Text>

                      <Badge colorScheme='orange'>{post.tag}</Badge>
                    </HStack>
                  </Stack>
                </Stack>
              </Link>
            ))}
          </SimpleGrid>
        </Container>
      </Box>
    </Box>
  );
};

export async function getStaticProps() {
  const res = await fetch(
    'INSERT GET ENDPOINT HERE'
  );
  const result = await res.json();
  const posts = result.rows;

  return {
    props: {
      posts,
    },
  };
}

export default Home;
```

Now, go over to `localhost:3000/create-post` page to create a couple more posts.

Check your homepage, and you should be able to see all the posts you created.

## Next Steps

In this tutorial, we built the basic blocks of a blog application - creating new posts and getting all posts. We set up a NextJS app, connected our MongoDB Atlas database in Buildable, and created our API endpoints.

We also learned how to consume our POST API and display data on the frontend. Feel free to add more enhancements to this application, like the ability to edit and delete posts. 

If you have any questions, comment below or reach out to us via our [Discord Community](https://discord.com/invite/47AJ42Wzys).

Till next time, keep building with Buildable!