How to Build a Forum App with Next.Js and Strapi CMS

Goals

Prerequisites

  • Node.js installed
  • Knowledge of NextJs

What is Strapi?

Strapi Setup

mkdir forumapp
cd forumapp
npx create-strapi-app@latest forum-backend --quickstart

Collection and Field Setup

  • Click on the “Create your first content type” button. It opens up a page to create our content. There is a modal where you can enter the name of your collection. Here, we will create a collection called strapi-forum.
  • Then, click on “Continue”. Another modal appears where you set the field for your collection type. Click on “Text”, then add the text fields.
  • Add “Title” in the text field and click on “Add another field”. Repeat the process for two additional fields, but we will use “Rich text” instead this time.
  • Click on the “Save” button. Now, in the Collection type sidebar, we see the “Strapi forums” collection.
  • Click on “content manager” from the left navigation menu, then click on the “Create new entry” button:
  • You’ll get a page where you can create an entry for the Forum app. Enter any text into these fields, then click on the save and publish buttons.
  • To enable our app to allow access to content without authorization, you must go to Settings then Roles.
  • Click on “Public”.
  • Click on Select all then Save.

Fetching the Collection

Building our Frontend

npx create-next-app forum
┣ 📂pages
┃ ┣ 📂api
┃ ┃ ┗ 📜hello.js
┃ ┣ 📂Components
┃ ┃ ┣ 📜Displayforum.js
┃ ┃ ┗ 📜Uploadforum.js
┃ ┣ 📜index.js
┃ ┣ 📜upload.js
┃ ┗ 📜_app.js
┣ 📂public
┃ ┣ 📜favicon.ico
┃ ┗ 📜vercel.svg
┣ 📂styles
┃ ┣ 📜globals.css
┃ ┗ 📜Home.module.css
import styles from "../styles/Home.module.css";
import Displayforum from "./Components/Displayforum";
export default function Home() {
return (
<div className={styles.container}>
<Displayforum />
</div>
);
}
import React, { useState } from "react";
import style from "../../styles/Home.module.css";
import Link from "next/link";
function Displayforum() {
const [show, setShow] = useState(false);
return (
<div>
<div className={style.topcont}>
<h1 className={style.heading}>Display forum</h1>
<div>
<Link href="/upload">
<button>Ask a question</button>
</Link>
<button>Login</button>
</div>
</div>
<h2 className={style.subheading}>Questions</h2>
<div className={style.userinfo}>
<p>Posted By: Victory Tuduo</p>
</div>
<div className={style.questioncont}>
<p className={style.question}>Description of the Question</p>
</div>
<div className={style.answercont}>
<h2 className={style.subheading}>Answers</h2>
<div className={style.inputanswer}>
<form>
<textarea type="text" placeholder="Enter your answer" rows="5" />
<button>Post</button>
</form>
</div>
<button className={style.showanswer} onClick={() => setShow(!show)}>
{show ? "Hide Answers" : "Show Answers"}
</button>
{show ? (
<div className={style.answers}>
<div className={style.eachanswer}>
<p className={style.username}>Miracle</p>
<p className={style.answertext}>Try doing it Like this</p>
</div>
</div>
) : null}
</div>
</div>
);
}
export default Displayforum;
import React from "react";
import Uploadforum from "./Components/Uploadforum";
function upload() {
return (
<div>
<Uploadforum />
</div>
);
}
export default upload;
import React from "react";
import style from "../../styles/Home.module.css";
import Link from "next/Link";
function Uploadforum() {
return (
<div className={style.uploadpage}>
<div className={style.topcont}>
<h1>Ask a question</h1>
<Link href="/">
<button>Forum</button>
</Link>
</div>
<div className={style.formcont}>
<form className={style.uploadform}>
<input type="text" placeholder="Enter your title" maxLength="74" />
<textarea type="text" placeholder="Enter your description" rows="8" />
<button>Submit Question</button>
</form>
</div>
</div>
);
}
export default Uploadforum;
.container {
min-height: 100vh;
padding: 0 0.5rem;
height: 100vh;
font-family: monospace;
}
/* display forum page */
.topcont {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 8px;
}
.topcont button,
.inputanswer button,
.formcont button,
.showanswer {
border: none;
color: #fff;
background: dodgerblue;
border-radius: 8px;
padding: 10px 15px;
outline: none;
margin: 8px;
}
.topcont button:hover {
cursor: pointer;
transform: scale(1.2);
}
.heading {
font-weight: bold;
}
.subheading {
font-weight: 500;
text-transform: uppercase;
}
.userinfo {
font-size: 18px;
font-weight: 600;
}
.questioncont {
min-height: 300px;
padding: 15px 14px;
box-shadow: 12px 12px 36px rgba(0, 0, 0, 0.12);
}
.answercont {
min-height: 300px;
padding: 5px 3px 5px 15px;
}
.answers {
height: 300px;
overflow-x: scroll;
}
.inputanswer {
margin-bottom: 8px;
}
.inputanswer textarea {
width: 100%;
resize: none;
padding: 5px 8px;
}
.showanswer {
border: 1px solid dodgerblue;
background: #fff;
color: dodgerblue;
transition: 0.4s ease-in-out;
}
.showanswer:hover {
background: dodgerblue;
color: #fff;
}
.eachanswer {
border-radius: 15px;
background: #e7e7e7;
padding: 8px 15px;
margin-bottom: 10px;
}
.username {
font-weight: bold;
text-transform: uppercase;
}
.answertext {
font-family: Montserrat;
font-size: 14px;
font-weight: 500;
}
/* upload a question page */
.uploadpage {
min-height: 100vh;
}
.formcont {
min-width: 100vw;
display: flex;
justify-content: center;
align-items: center;
}
.uploadform {
display: flex;
flex-direction: column;
min-width: 500px;
padding-top: 10px;
}
.uploadform input,
.uploadform textarea {
resize: none;
width: 100%;
margin: 8px;
padding: 5px;
}

Getting Data from Strapi

Setting up Our Fetch Request

npm install axios
import axios from "axios";
const url = "http://localhost:1337/api/strapi-forums";
export const readForum = () => axios.get(url);
import { readForum, createQuestion } from "./api";

Fetching Data from Strapi

import { react, useState, useEffect } from "react";
...
const [question, setQuestions] = useState({});
const [response, setResponse] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await readForum();
setResponse(result.data.data);
};
fetchData();
}, []);
<Displayforum response={response} />

Displaying Data from Strapi

//...
function Displayforum({ response }) {
//...
{response.map((response, index) => (
<div key={index}>
<h2 className={style.subheading}>{response.attributes.Title}</h2>
<div className={style.userinfo}>
//...
<p className={style.answertext}>Try doing it Like this</p>
</div>
</div>
) : null}
</div>
</div>
))}
response.attributes.Username
<p>Posted By: {response.attributes.Username}</p>
...
<p className={style.question}>{response.attributes.Questions}</p>
...
{response.attributes.Answers.map((answers, i) => (
<div className={style.eachanswer} key={i}>
<p className={style.username}>{response.attributes.Answername}</p>
<p className={style.answertext}>{answers}</p>
</div>
))}
npm run dev

Adding Data to Strapi

import { React, useState } from "react";
...
const [name, setName] = useState("");
const [description, setDescription] = useState("");
<input
type="text"
placeholder="Enter your title"
maxLength="74"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<textarea
type="text"
placeholder="Enter your description"
rows="8"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<button  onClick={(e) => {
e.preventDefault();
sendData();
}}
}>Submit Question</button>
const sendData = () => {
};
import axios from "axios";
const url = "http://localhost:1337/api/strapi-forums";
const sendData = () => {
axios.post(url, {
data: {
Title: name,
Questions: description,
},
});

Adding New Answers

import axios from "axios";
//...
const [answer, setAnswer] = useState("")
const [id, setId] = useState("");
const [a, formerArray] = useState([]);
<textarea
type="text"
placeholder="Enter your answer"
rows="5"
value={answer}
onChange={(e) => {
formerArray(response.attributes.Answers);
setAnswer(e.target.value);
setId(response.id);
}}
/>
<button
onClick={(e) => {
submitAnswer();
}}
>
}}>Post</button>
const submitAnswer = () => {
try {
axios.put(`http://localhost:1337/api/strapi-forums/${id}`, {
Answers: [...a, answer],
});
} catch (error) {
console.log(error);
}
};

User Authentication with NextAuth

npm i next-auth
import NextAuth from "next-auth"
import GoogleProvider from "next-auth/providers/google"


export default NextAuth({
secret: process.env.SECRET,
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_ID,
clientSecret: process.env.GOOGLE_SECRET,
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth?prompt=consent&access_type=offline&response_type=code',
}),
],
})
...
import { SessionProvider } from "next-auth/react"
function MyApp({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
);
}
export default MyApp;
import {signIn, signOut, useSession} from 'next-auth/react'
//...
const { data: session } = useSession()
return(
<div>
{!session && (
<>
<h1>Sign in to access forum</h1>
<button onClick={() => signIn()}>Sign In</button>
</>
)}
{session && (
<>
{/*rest of our app*/}
</>
)}
</div>
//...
<Link href="/upload">
<button>Ask a question</button>
</Link>
<button onClick={() => signOut()}>Signout</button>
//...
GOOGLE_CLIENT_ID: id
GOOGLE_CLIENT_SECRET: secret
secret: any string
import { getSession, useSession } from 'next-auth/react'
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
context.res.writeHead(302, { Location: '/' });
context.res.end();
return {};
}
return {
props: {
user: session.user,
}
}
}
export default Upload;

Adding Username to Collection

//...
axios.put(`http://localhost:1337/api/strapi-forums/${id}`, {
Answers: [...a, answer],
Answername: session.user.name,
});
const { data: session } = useSession();
<Uploadforum session={session} />
function Uploadforum({session}) {
//...
axios.post(url, {
data: {
Title: name,
Questions: description,
Answers: [],
Username: session.user.name,
},
});
<div className={style.answers}>
{response.attributes.Answers.map((answers, i) => (
<div className={style.eachanswer} key={i}>
<p className={style.username}>{answers[0]}</p>
<p className={style.answertext}>{answers[1]}</p>
</div>
))}
...
axios.put(`http://localhost:1337/api/strapi-forums/${id}`, {
Answers: [...a, [session.user.name, answer]],
});
} catch (error) {
console.log(error);

Conclusion

Resources

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store