Build a Full-Stack Portfolio Site with React and Express: A Step-by-Step Guide for Self-Taught DevelopersNew
• Updated 5/26/2026 • ReactNode.jsExpressPortfolioFull-StackJavaScriptWeb Development
Introduction
A strong developer portfolio should do more than display your name, skills, and project screenshots. It should prove that you can build real software from front end to back end. In this guide, you will build a full-stack portfolio website using React for the user interface and Express for the API.
This project is practical for self-taught developers because it demonstrates the same skills used in production applications: routing, API design, authentication, protected actions, environment variables, deployment, and clean project structure.
What You Will Build
You will create a portfolio app with two main areas:
- A public portfolio page where visitors can view your projects.
- A protected admin area where you can add, update, and delete projects.
The first version will use an in-memory data store to keep the tutorial focused. After that, you can replace it with MongoDB, PostgreSQL, MySQL, or another database.
Why This Project Matters
A full-stack portfolio helps you show more than design ability. It shows that you understand how web applications actually work.
- You can create reusable React components.
- You can design REST API endpoints.
- You can connect a front end to a back end.
- You understand authentication basics.
- You can deploy a working application online.
- You can explain technical decisions during interviews.
Recommended Project Structure
Keep the front end and back end separated. This makes the project easier to deploy, maintain, and explain.
portfolio-app/
frontend/
src/
components/
pages/
api/
App.jsx
backend/
server.js
.env
package.json
Prerequisites
Before starting, you should be comfortable with the basics of HTML, CSS, JavaScript, npm, Git, and GitHub. You do not need advanced backend experience, but you should understand what an API request is.
Step 1: Create the React Front End
You can use Vite for a faster modern React setup.
npm create vite@latest frontend -- --template react
cd frontend
npm install
npm run dev
Create basic components for your portfolio:
ProjectListfor displaying projects.ProjectCardfor showing one project.ProjectFormfor adding or editing projects.Loginfor admin authentication.
Step 2: Create the Express Back End
Now create the API server.
mkdir backend
cd backend
npm init -y
npm install express cors dotenv jsonwebtoken bcryptjs
npm install --save-dev nodemon
Add a development script to your backend package.json:
{
"scripts": {
"dev": "nodemon server.js",
"start": "node server.js"
}
}
Step 3: Build the Basic API
Create server.js inside the backend folder.
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const jwt = require("jsonwebtoken");
const app = express();
const PORT = process.env.PORT || 5000;
app.use(cors());
app.use(express.json());
let projects = [
{
id: 1,
title: "Task Manager App",
description: "A full-stack task management app built with React and Express.",
techStack: ["React", "Express", "Node.js"],
liveUrl: "https://example.com",
repoUrl: "https://github.com/example/task-manager"
}
];
app.get("/api/projects", (req, res) => {
res.json(projects);
});
app.post("/api/projects", authenticateToken, (req, res) => {
const project = {
id: Date.now(),
title: req.body.title,
description: req.body.description,
techStack: req.body.techStack || [],
liveUrl: req.body.liveUrl || "",
repoUrl: req.body.repoUrl || ""
};
projects.push(project);
res.status(201).json(project);
});
app.put("/api/projects/:id", authenticateToken, (req, res) => {
const projectId = Number(req.params.id);
const index = projects.findIndex((project) => project.id === projectId);
if (index === -1) {
return res.status(404).json({ message: "Project not found" });
}
projects[index] = {
...projects[index],
...req.body
};
res.json(projects[index]);
});
app.delete("/api/projects/:id", authenticateToken, (req, res) => {
const projectId = Number(req.params.id);
projects = projects.filter((project) => project.id !== projectId);
res.json({ message: "Project deleted successfully" });
});
function authenticateToken(req, res, next) {
const authHeader = req.headers.authorization;
const token = authHeader && authHeader.split(" ")[1];
if (!token) {
return res.status(401).json({ message: "Access token required" });
}
try {
const user = jwt.verify(token, process.env.JWT_SECRET);
req.user = user;
next();
} catch (error) {
res.status(403).json({ message: "Invalid or expired token" });
}
}
app.listen(PORT, () => {
console.log(`API running on port ${PORT}`);
});
Step 4: Add a Simple Login Endpoint
For a portfolio admin panel, you can start with one admin user stored through environment variables. This keeps the tutorial simple while still teaching JWT authentication.
Create a .env file in your backend folder:
PORT=5000
JWT_SECRET=replace_this_with_a_long_random_secret
ADMIN_EMAIL=admin@example.com
ADMIN_PASSWORD=change_this_password
Add this login route above app.listen:
app.post("/api/login", (req, res) => {
const { email, password } = req.body;
const isValidAdmin =
email === process.env.ADMIN_EMAIL &&
password === process.env.ADMIN_PASSWORD;
if (!isValidAdmin) {
return res.status(401).json({ message: "Invalid login credentials" });
}
const token = jwt.sign(
{ email },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
res.json({ token });
});
For a real production app, do not store plain passwords. Use a database and hash passwords with bcryptjs.
Step 5: Connect React to the API
Create an API helper file in frontend/src/api/projects.js.
const API_URL = import.meta.env.VITE_API_URL || "http://localhost:5000/api";
export async function getProjects() {
const response = await fetch(`${API_URL}/projects`);
if (!response.ok) {
throw new Error("Failed to fetch projects");
}
return response.json();
}
export async function createProject(project, token) {
const response = await fetch(`${API_URL}/projects`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`
},
body: JSON.stringify(project)
});
if (!response.ok) {
throw new Error("Failed to create project");
}
return response.json();
}
Add this to your frontend .env file:
VITE_API_URL=http://localhost:5000/api
Step 6: Display Projects in React
Create a simple project list component.
import { useEffect, useState } from "react";
import { getProjects } from "../api/projects";
export default function ProjectList() {
const [projects, setProjects] = useState([]);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadProjects() {
try {
const data = await getProjects();
setProjects(data);
} catch (error) {
setError("Unable to load projects right now.");
} finally {
setLoading(false);
}
}
loadProjects();
}, []);
if (loading) return <p>Loading projects...</p>;
if (error) return <p>{error}</p>;
return (
<section>
<h2>Projects</h2>
{projects.map((project) => (
<article key={project.id}>
<h3>{project.title}</h3>
<p>{project.description}</p>
<p>Tech stack: {project.techStack.join(", ")}</p>
</article>
))}
</section>
);
}
Step 7: Handle Authentication on the Front End
After login, store the JWT so protected requests can use it. For a learning project, localStorage is acceptable. For more secure production apps, consider HTTP-only cookies and stronger session handling.
export async function login(email, password) {
const response = await fetch(`${API_URL}/login`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({ email, password })
});
if (!response.ok) {
throw new Error("Invalid email or password");
}
const data = await response.json();
localStorage.setItem("token", data.token);
return data;
}
Step 8: Deploy the Application
Deploy the Front End
- Push the frontend folder to GitHub.
- Deploy it with Vercel, Netlify, or another static hosting provider.
- Add your production
VITE_API_URLenvironment variable.
Deploy the Back End
- Push the backend folder to GitHub.
- Deploy it with Render, Railway, Fly.io, or another Node-friendly platform.
- Add
JWT_SECRET,ADMIN_EMAIL, andADMIN_PASSWORDas environment variables. - Update CORS settings so your API accepts requests from your deployed frontend domain.
Common Mistakes and How to Avoid Them
- Using Create React App for new projects: Prefer Vite for a faster and more modern setup.
- Hard-coding API URLs: Use environment variables for development and production URLs.
- Committing secrets: Never commit
.envfiles to GitHub. - Ignoring CORS: Configure your backend to allow requests from your frontend domain.
- No loading or error states: Always show useful messages when data is loading or when requests fail.
- Overbuilding too early: Start with a simple working version, then improve it.
- Weak project descriptions: Explain what each project solves, what tools you used, and what you learned.
Actionable Tips for a Strong Developer Portfolio
- Include only your best projects, not every tutorial you completed.
- Add a short case study for each major project.
- Show live demos and GitHub repository links when possible.
- Write clean README files for your repositories.
- Use meaningful commit messages.
- Make the site responsive on mobile devices.
- Add basic SEO tags such as title, description, and Open Graph metadata.
- Keep your contact form simple and reliable.
Next Improvements
Once the first version works, improve it with features that show deeper full-stack ability:
- Replace the in-memory store with MongoDB, PostgreSQL, or MySQL.
- Add image uploads for project screenshots.
- Create a dashboard for editing portfolio content.
- Add form validation with clear error messages.
- Add rate limiting to protect login routes.
- Add automated tests for API endpoints.
- Use GitHub Actions for basic CI checks.
Conclusion
Building a full-stack portfolio with React and Express is one of the most practical projects for self-taught developers. It proves that you can create a user interface, build an API, protect admin actions, manage environment variables, and deploy a real application.
Start with a simple version, deploy it, then improve it over time. A finished and live project is more valuable than a complex idea that never gets published.
Support
Keep CompileQuestHub free
If this blog helped you, support more open tutorials and code examples.
Need More?
Request a topic or report an issue
Use the contact form to request follow-up tutorials or report broken code, missing files, or outdated links.
Page Info
Freshness and topics
Topic: Full-Stack JavaScript
Difficulty: Intermediate
Reading time: 9 min read
Published: 5/25/2026
Updated: 5/26/2026
Before You Start
Prerequisites
- Basic HTML and CSS
- Fundamental JavaScript knowledge
- Familiarity with Git and GitHub
Outcome
What you will learn
- Set up a modern React front-end with Vite
- Create a RESTful API using Express
- Connect front-end to back-end with fetch
- Implement basic JWT authentication
- Use environment variables safely
- Deploy a full-stack app to modern hosting platforms
Related
Keep going with nearby resources
How to Land Your First Web or Full-Stack Developer Job After College in 2026
A practical 2026 career guide for new graduates who want to land their first web or full-stack developer job with a strong portfolio, targeted resume, networking strategy, and interview prep plan.
How to Land a Web or Full-Stack Developer Job in 2026 Without a College Degree
A practical roadmap for self-taught developers who want to get hired in web or full-stack development by building real projects, improving their portfolio, networking, and preparing for interviews.
Roadmap to Becoming a Web Developer: Skills, Tools, and First Projects
A practical beginner roadmap for becoming a web developer, covering core skills, essential tools, learning milestones, portfolio projects, common mistakes, and next career steps.
Next Step

