Open In App

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.

Screenshot-2024-03-17-144218

Prerequisites:

Approach to create Freelancer Portfolio Builder Application:

User-friendly Interface:

Real-time Updates using React Components:

Integration with MongoDB:

RESTful API Endpoints:

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):Screenshot-2024-03-17-143951

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):

Screenshot-2024-03-17-144025

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:

node server.js
 npm start

Output:

2413-ezgifcom-video-to-gif-converter

Screenshot-2024-03-23-105619


Article Tags :