How To Create A Full-Stack Application With Next - Js - A Step-By-Step Tutorial For Beginners
How To Create A Full-Stack Application With Next - Js - A Step-By-Step Tutorial For Beginners
Getting Started
To keep things simple and familiar for you, we will avoid using a
database and instead utilize local JSON data. By removing the
complexity of database integration, we can focus on mastering the
fundamental concepts of Next.js.
Application Preview
Getting Started
To get started with this tutorial, I highly recommend using the
Forum Donate
provided starter boilerplate that I specifically created for this tutorial.
Support our charity and our mission. Donate to freeCodeCamp.org.
It saves your valuable time by already including the necessary
dependencies and folder structure, eliminating the need to set up your
project from scratch.
Simply clone the starter boilerplate from the GitHub repository and
then follow along with the tutorial. This way, you can focus on learning
and implementing the concepts without getting caught up in setup
details.
Once you have set up the starter boilerplate and successfully run it on
your local machine, you should be able to see the initial page. This
page marks the beginning of our tutorial and will serve as the starting
point for our journey.
From here, we will gradually build upon the existing code and
implement some cool features into our application. Let's dive in and
get started right away!
How to Create a Shared Layout Forumin Donate
Next.js
Support our charity and our mission. Donate to freeCodeCamp.org.
Often in your applications, you have elements that are shared across
multiple pages, such as a navigation bar or a footer. Manually adding
these elements to each page can be tedious and error-prone.
Fortunately, Next.js provides a convenient way to create shared
layouts that can be reused across our entire application.
The first type of layout is called the Root Layout. As the name
suggests, this layout is shared across all pages in our application. It
serves as the top-most layout and provides a consistent structure for
our entire app. The Root Layout is required and we need to ensure
that it includes the necessary HTML and body tags.
// 📁 app/layout.js
const inter = Inter({ subsets: ['latin'] })
The component you see here is the Root Layout component, which
plays a crucial role in creating a shared layout for your entire
application. Let's take a closer look at its structure and functionality.
Within the body tag, you include the Navigation component, which
is imported from the components directory. This component
represents your navigation bar and will be shared across all pages of
your application. By including it here, you ensure that it is displayed
consistently throughout your app.
// 📁 components/Navigation.jsx
export const Navigation = () => {
return (
<div className="sticky top-0 backdrop-blur-xl bg-[rgba(0,0,0,0.
<Container className="flex justify-between py-5">
<Link href="/">
<Image src="/logo.png" alt="Family Guy" width={70} height
</Link>
<Link
href="/quiz"
className="flex items-center justify-center gap-1 px-5 fo
>
<TbArrowBigRightFilled className="text-lg" />
Take a Quiz
</Link>
</Container>
</div>
)
}
It's important to keep in mind that within a single directory, you can
have either a UI route or an API route, but not both. This clear
separation allows for a clean and organized structure while building
your Next.js application.
In the next section, you're going to create your first API route in
Next.js. API routes in Next.js provide a simple and convenient way to
create server-side endpoints within your application.
With API routes, you can define custom routes that handle HTTP
requests and responses, allowing you to fetch or modify data, perform
server-side computations, or integrate with external services.
This section will guide you through the process of creating the
Characters API route. Simply open up the
app/api/characters/route.js file and add the following code:
// 📁 app/api/characters/route.js
export async function GET() {
return NextResponse.json({ characters: characters.data })
}
Now, it's time to test your API route and ensure that everything is
functioning correctly. To make this process simpler, you will use the
browser itself to make the API request. Open your browser and enter
the following URL: http://localhost:3000/api/characters.
Upon doing so, you will be directed to a page where you can observe
the results of the API request. This step allows us to verify that the
API route is working as expected and that it's successfully fetching the
character data:
// 📁 app/page.jsx
async function getAllCharacters() {
const data = await fetch(`${endpoint}/characters`)
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
return (
<main>
<Container className="grid grid-cols-2 gap-1 py-5 md:grid-col
{data?.characters?.map(item => {
return (
<Link
href={`/characters/${item.slug}`}
key={item.name}
className="overflow-hidden rounded-md"
>
<Image
src={item.avatar}
alt=""
className="transition-all duration-500 hover:scale-
width={500}
Forum Donate
height={500}
/>
Support our charity and our mission. Donate to freeCodeCamp.org.
</Link>
)
})}
</Container>
</main>
)
}
In the above code snippet, you have a React component called Page
that is defined as an asynchronous function. This component is
responsible for rendering the homepage UI.
Inside the Container , you map over the characters array in the
data object and generate a list of items. For each character, we create
a "Link" component that serves as a clickable link to a specific
Forum Donate
character's page. The link's URL is generated based on the character's
Support our charity and our mission. Donate to freeCodeCamp.org.
slug property.
Within the Link , you have an Image component that displays the
character's avatar image.
Homepage
Your homepage is now looking fantastic, but you may have noticed
something unusual about the way we fetched the data. Typically, you
might be familiar with using the useEffect hook to fetch data from an
API. But in this case, you didn't use any hooks – yet your code is
functioning perfectly.
In the next section, we will take a closer look at what exactly happened
within this component. By examining the code and its execution, you
will gain a deeper understanding of the Next.js mechanisms.
The App Router basically enables you to run React code on the server
by default, so you are fetching data on the server and only returning
the static HTML to the client. This means that we have a Server
Component that retrieves data from the server and renders its
content on the server side.
If you want to use client-side features, you have to specify that in your
component file by adding "use client" at the top of the file.
You can make a quick adjustment in your project. First, navigate to the
app/page.jsx file and locate the getAllCharacters function at the
top. Cut out this function from the file.
// 📁 lib/characters.js
import { endpoint } from '@/utils/endpoint'
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
return (
<main>
//content went here ...
</main>
)
}
This way, you are going to have access to this fetch function
throughout your whole codebase.
In this section, you will be creating a dynamic API route. This route will
enable you to fetch data for each character individually and
subsequently build a user interface (UI) to showcase these characters
to your users.
// 📁 api/characters/[slug]/route.js
export async function GET(req, { params }) {
try {
const character = characters.data.find(item => item.slug === pa
if (!character) {
return new NextResponse('not found', { status: 404 })
}
return NextResponse.json({
character,
character_qoutes: character_qoutes.length > 0 ? character_qou
})
} catch (error) {
return new NextResponse('Internal Server Error', { status: 500
}
}
Now you can test this endpoint to see the result, open up
http://localhost:3000/api/characters/peter-griffin in your browser
and you should be able to see the following JSON data:
Forum Donate
// 📁 app/characters/[slug]/page.jsx
import { getAllCharacters } from '@/lib/characters'
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
Forum Donate
export default async function Page({ params }) {
Support our charity and our mission. Donate to freeCodeCamp.org.
const { character, character_qoutes } = await getCharacterBySlug(
return (
<Container className="flex flex-col gap-5 py-5" as="main">
<div className="flex flex-col gap-2">
<h1 className="text-2xl font-semibold capitalize">{characte
<ul className="flex gap-1 text-sm">
{character.occupations.map(item => {
return (
<li
key={item}
className="p-2 text-gray-300 bg-gray-800 rounded-md
>
{item}
</li>
)
})}
</ul>
</div>
<p className="text-sm leading-6">{character.description}</p>
<ul className="grid gap-2 sm:grid-cols-2">
{character.images.map(image => {
return (
<li
key={image}
className="relative flex overflow-hidden bg-gray-900
>
<Image
className="transition-all duration-500 hover:scale-
src={image}
alt=""
width={760}
height={435}
/>
</li>
)
})}
</ul>
{character.skills && (
<>
<h2 className="text-xl font-bold">Power and Skills</h2>
<ul className="flex flex-wrap gap-1">
{character.skills.map(item => {
return (
<li
Forum Donate
className="flex justify-center flex-grow px-2 py-
Support our charity and our mission. Donate to freeCodeCamp.org.
key={item}
>
{item}
</li>
)
})}
</ul>
</>
)}
{character_qoutes && (
<>
<h2 className="text-xl font-bold">Famous Qoutes</h2>
<ul className="grid gap-5">
{character_qoutes.map((item, idx) => {
return (
<li
className="p-2 italic text-gray-400 border-l-4 bo
key={item.idx}
>
{item.qoute}
</li>
)
})}
</ul>
</>
)}
</Container>
)
}
Don't be intimidated by the length of the code you see here! It may
seem overwhelming at first, but it's actually quite straightforward.
Let's take a deeper look at what we did in this code:
Next.js will then use this information to generate the static HTML files
for these paths during the build process. This allows the pages to be
served as static files, improving performance and SEO.
The returned data is then used to populate the UI, which includes
displaying the character's name, occupations, description, images,
power and skills (if available), and famous quotes (if available).
// 📁 app/api/quiz/random/route.js
export async function GET() {
try {
Forum
const random = Math.floor(Math.random() * questions.data.length
Donate
return NextResponse.json({
Support our charity and our mission. Donate to freeCodeCamp.org.
randomQuestion: questions.data[random].id,
})
} catch (error) {
return new NextResponse('Internal Server Error', { status: 500
}
}
In this Next.js API route, you are implementing the logic to fetch a
random question from a set of questions stored in a JSON file called
quiz.json . First, we import the questions data from the JSON file
and the NextResponse object from the Next.js server package.
You will leverage the API route you just created to dynamically
redirect users to a new question each time they start the quiz.
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
return (
<Container
as="main"
className="flex flex-col gap-5 py-5 md:flex-row-reverse md:ju
>
<div className="relative overflow-hidden rounded-2xl">
<div className="md:w-[24rem]">
<Image src="/wallpaper.jpg" alt="" width={700} height={70
</div>
<div className="absolute top-0 bottom-0 left-0 right-0 bg-g
</div>
This code sets up the UI for the quiz introduction section, fetches a
random question from the API, and provides a button for users to
start the quiz.
In the this code, you might have noticed a change where we pass a
parameter to the fetch method: { cache: 'no-store' } .
// 📁 app/api/quiz/[id]
export async function GET(req, { params }) {
try {
const question = questions.data.find(item => item.id === params
if (!question) {
return new NextResponse('not found', { status: 404 })
}
return NextResponse.json({
question: rest,
})
} catch (error) {
return new NextResponse('Internal Server Error', { status: 500
}
}
Now you can test this API route in your browser by opening up your
local server http://localhost:3000/api/quiz/CfQnf3lH56:
http://localhost:3000/api/quiz/CfQnf3lH56
// 📁 app/api/quiz/answer/[id]/route.js
export async function GET(req, { params }) {
try {
Forum Donate
const question = questions.data.find(item => item.id === params
return NextResponse.json({
correct: correct_answer,
random: filteredQuestions[random].id,
})
} catch (error) {
return new NextResponse('Internal Server Error', { status: 500
}
}
To suggest the next question, the code removes the current question
from the available pool of questions by filtering it out. It then
generates a random index within the range of the remaining questions.
Using this random index, a new question is selected as a suggestion for
the next question.
// 📁 app/quiz/[id]/page.jsx
async function getQuizQuestion(id) {
const data = await fetch(`${endpoint}/quiz/${id}`)
if (!data.ok) {
throw new Error('Failed to fetch data')
}
return data.json()
}
return (
<Container as="main" className="flex flex-col gap-5 py-5">
<h1 className="text-lg font-semibold">{question.title}</h1>
<Answer answers={question.answers} questionId={params.id} />
</Container>
Forum Donate
)
Support our charity and our mission. Donate to freeCodeCamp.org.
}
Question UI route
// 📁 components/Answer.jsx
'use client'
useEffect(() => {
let subscribed = true
if (selected) {
setLoading(true)
fetch(`/api/quiz/answer/${questionId}`)
.then(res => res.json())
.then(data => {
setLoading(false)
if (subscribed) {
setData(data)
}
})
}
return () => {
console.log('cancelled!')
subscribed = false
}
}, [questionId, selected])
return (
<>
<ul className="grid grid-cols-2 gap-2 md:grid-cols-4">
{answers.map(item => {
const isLoading = selected === item && loading
const isWrong =
selected === item && data && data?.correct !== selected
const isCorrect = data?.correct === item
return (
<li key={item}>
<button
disabled={data || loading}
onClick={() => setSeleceted(item)}
className={cn(
'p-2 rounded-md items-center justify-between w-f
Forum Donate
isLoading && 'animate-pulse',
isWrong ? 'bg-red-700' : 'bg-slate-800',
Support our charity and our mission. Donate to freeCodeCamp.org.
isCorrect && 'outline text-green-500',
)}
>
{item}
{isCorrect && <FaCheck />}
{isWrong && <MdNearbyError />}
</button>
</li>
)
})}
</ul>
{data?.random && (
<Link
href={`/quiz/${data.random}`}
className="flex items-center gap-1 text-blue-400"
>
<FiRepeat className="mt-1" />
Do it again
</Link>
)}
</>
)
}
Conclusion
Here is the end! You've successfully built your first full-stack
application using Next.js. Throughout this step-by-step tutorial, you
learned the basics of Next.js, exploring its powerful features and
gaining the necessary knowledge to create modern web applications.
Through this tutorial, you not only built a functional app, but you also
gained the confidence to start creating our own full-stack applications
with Next.js. You learned about routing, server-side rendering, API
integration, and more.
Now that you have a solid foundation in Next.js, the possibilities are
endless. You can continue exploring advanced topics, such as database
integration, authentication, and deployment, to take your applications
to the next level.
You can follow me on Twitter where I share more useful Forum
tips on web Donate
development. Happy coding!
Support our charity and our mission. Donate to freeCodeCamp.org.
Yazdun Fadali
Frontend developer, With passion for creating cutting-edge digital
experiences and a talent for crafting smooth, intuitive user interfaces !
If you read this far, thank the author to show them you care.
Say Thanks
Trending Guides
Mobile App
Our Charity
About Alumni Network Open Source Shop Support Sponsors Academic Honesty