Freelancer Portfolio Builder Application using MERN Stack
In today’s digital age, having a professional portfolio is essential for freelancers to showcase their skills and attract potential clients. In this article, we’ll dive into the process of building a Freelancer Portfolio Builder using the MERN (MongoDB, Express.js, React.js, Node.js) stack. This project will allow freelancers to create, edit, and manage their portfolios effectively.
Output Preview: Let us have a look at how the final output will look like.
Prerequisites:
Approach to create Freelancer Portfolio Builder Application:
User-friendly Interface:
- The user interface will be designed to provide a seamless experience for users to add, edit, and delete portfolios.
- Use modern design principles and UI frameworks like Material UI or Bootstrap for a clean and responsive layout.
- Implement intuitive navigation and user interactions to ensure ease of use.
Real-time Updates using React Components:
- Utilize React’s component-based architecture to create modular and reusable UI components.
- Implement real-time updates using React Hooks (e.g., useState, useEffect) or Context API for managing state and triggering re-renders when data changes.
- Use WebSocket or a real-time database like Firebase Firestore for instant updates if required.
Integration with MongoDB:
- Set up a MongoDB database to store portfolio data.
- Use Mongoose (a MongoDB object modeling tool for Node.js) to define schemas, models, and perform database operations.
- Establish a connection to MongoDB using mongoose.connect in the backend server.
RESTful API Endpoints:
- Create RESTful API endpoints in Node.js using Express.js to handle CRUD operations (Create, Read, Update, Delete) for portfolios.
- Define routes for handling HTTP requests such as GET, POST, PUT, DELETE for portfolios.
- Use middleware like body-parser or express.json to parse incoming request bodies as JSON.
Steps to Create the Frontend:
Step 1: Develop frontend using React.js for the user interface.
mkdir freelancer-portfolio-builder-frontend
cd freelancer-portfolio-builder-frontend
npx create-react-app .
Step 2: Connect the frontend and backend using Axios for making API requests.
Project Structure(Frontend):
The updated dependencies in package.json file will look like:
"dependencies": {
"@testing-library/jest-dom": "^5.17.0",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.6.8",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
}
Example: Below is the frontend code:
import React, { useState, useEffect } from 'react';
import axios from 'axios';
const App = () => {
const [portfolios, setPortfolios] = useState([]);
const [title, setTitle] = useState('');
const [description, setDescription] = useState('');
const [skills, setSkills] = useState('');
const [githubLink, setGithubLink] = useState('');
useEffect(() => {
axios.get('http://localhost:5000/api/portfolios')
.then(res => setPortfolios(res.data))
.catch(err => console.error(err));
}, []);
const addPortfolio = (newPortfolio) => {
setPortfolios([...portfolios, newPortfolio]);
};
const handleSubmit = (e) => {
e.preventDefault();
const newPortfolio =
{
title, description,
skills: skills.split(','), githubLink
};
axios.post('http://localhost:5000/api/portfolios', newPortfolio)
.then(res => {
alert(res.data.message);
addPortfolio(newPortfolio);
setTitle('');
setDescription('');
setSkills('');
setGithubLink('');
})
.catch(err => console.error(err));
};
const handleDelete = (id) => {
axios.delete(`http://localhost:5000/api/portfolios/${id}`)
.then(res => {
alert(res.data.message);
setPortfolios(portfolios.filter(p => p._id !== id));
})
.catch(err => console.error(err));
};
return (
<div className="App"
style={
{
maxWidth: '800px', margin: '0 auto',
padding: '20px'
}}>
<h1 style={{ textAlign: 'center' }}>
Freelancer Portfolio Builder
</h1>
<div style={{ marginBottom: '20px' }}>
<h2>Add Portfolio</h2>
<form onSubmit={handleSubmit}
style={
{
display: 'flex',
flexDirection: 'column',
gap: '10px'
}}>
<input type="text"
placeholder="Title"
value={title}
onChange={
(e) =>
setTitle(e.target.value)
} required style={{ padding: '5px' }} />
<input type="text"
placeholder="Description"
value={description}
onChange={
(e) =>
setDescription(e.target.value)
}
required
style={{ padding: '5px' }} />
<input type="text"
placeholder="Skills (comma-separated)"
value={skills}
onChange={(e) => setSkills(e.target.value)}
required
style={{ padding: '5px' }} />
<input type="text" placeholder="Github Link"
value={githubLink}
onChange={(e) => setGithubLink(e.target.value)}
required style={{ padding: '5px' }} />
<button type="submit"
style={
{
padding: '5px',
backgroundColor: '#007bff',
color: '#fff',
border: 'none',
cursor: 'pointer'
}}>Add Portfolio</button>
</form>
</div>
<div>
<h2>Portfolios</h2>
{portfolios.map(portfolio => (
<div key={portfolio._id}
style={
{
border: '1px solid #ccc',
borderRadius: '5px',
padding: '10px', marginBottom: '10px'
}}>
<h3>{portfolio.title}</h3>
<p>{portfolio.description}</p>
<p>Skills: {portfolio.skills.join(', ')}</p>
<p>Github Link: {portfolio.githubLink}</p>
<button
onClick={
() =>
handleDelete(portfolio._id)
}
style={
{
padding: '5px',
backgroundColor: 'red',
color: '#fff', border: 'none',
cursor: 'pointer'
}}>Delete</button>
</div>
))}
</div>
</div>
);
};
export default App;
Steps to Create Backend:
Step 1: Initialize a new Node.js project and install necessary dependencies.
mkdir freelancer-portfolio-builder
cd freelancer-portfolio-builder
npm init -y
npm install express mongoose dotenv cors
Step 2: Create backend API routes using Express.js for CRUD operations.
Project Structure(Backend):
The updated dependencies in package.json file will look like:
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.18.3",
"mongoose": "^8.2.2"
}
Example: Below is the backend code:
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 5000;
// Middleware
app.use(cors());
app.use(express.json());
// MongoDB connection
mongoose.connect('mongodb://localhost:27017/freelancerportfolio', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
const connection = mongoose.connection;
connection.once('open', () => {
console.log('MongoDB connection established successfully');
});
// MongoDB Model
const portfolioSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
skills: {
type: [String],
required: true
},
githubLink: {
type: String,
required: true
}
});
const Portfolio = mongoose.model('Portfolio', portfolioSchema);
// API Routes
app.get('/api/portfolios', async (req, res) => {
try {
const portfolios = await Portfolio.find();
res.json(portfolios);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.post('/api/portfolios', async (req, res) => {
try {
const { title, description, skills, githubLink } = req.body;
const newPortfolio = new Portfolio({
title,
description,
skills,
githubLink
});
await newPortfolio.save();
res.json({ message: 'Portfolio created successfully' });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
app.put('/api/portfolios/:id', async (req, res) => {
try {
const { title, description, skills, githubLink } = req.body;
await Portfolio.findByIdAndUpdate(req.params.id, {
title,
description,
skills,
githubLink
});
res.json({ message: 'Portfolio updated successfully' });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
app.delete('/api/portfolios/:id', async (req, res) => {
try {
await Portfolio.findByIdAndDelete(req.params.id);
res.json({ message: 'Portfolio deleted successfully' });
} catch (err) {
res.status(400).json({ error: err.message });
}
});
// Start the server
app.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
Steps to Run the Application:
- Start the Node.js server using
node server.js
- Navigate to the frontend directory and start the React development server using
npm start
- Access the application in your web browser at http://localhost:3000
Output:
- Browser Output
- Data Saved in Database:
Contact Us