Assignment Backend
Node.js Can be used for your assignment backend to serve event data and accept feedback.
import express from "express";
import cors from "cors";
import { fileURLToPath } from "url";
import path, { dirname } from "path";
import fs from "fs/promises";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
let experiences = [];
let feedbacks = [];
/**
* Fetch data from JSON file and store in memory (simulating a database)
*/
try {
const data = await fs.readFile(
path.join(__dirname, "data", "experiences.json"),
"utf-8"
);
experiences = JSON.parse(data);
console.log("Experiences loaded successfully");
} catch (error) {
console.error("Error loading experiences:", error);
process.exit(1);
}
try {
const data = await fs.readFile(
path.join(__dirname, "data", "feedback.json"),
"utf-8"
);
feedbacks = JSON.parse(data);
console.log("Feedback loaded successfully");
} catch (error) {
console.error("Error loading feedback:", error);
process.exit(1);
}
const app = express();
const PORT = 3000;
// middleware
app.use(cors());
app.use(express.json());
/**
* Submits feedback for a specific experience
* @param {Number} expid
* @param {String} name
* @param {String} feedback
*/
function submitFeedback(expid, name, feedback) {
// Replicate a database ID auto-increment based on array length
let id = feedbacks.length > 0 ? feedbacks[feedbacks.length - 1].id + 1 : 999;
// Store feedback in memory
feedbacks.push({ id, name, expid, feedback });
console.log("Feedback submitted:", { id, name, feedback });
}
// endpoints
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.get("/api/experiences", (req, res) => {
console.log("backend: /api/experiences called");
res.json(experiences);
});
app.get("/api/feedback/", (req, res) => {
console.log("backend: /api/feedback called");
res.json(feedbacks);
});
// POST method
app.post("/api/feedback/", (req, res) => {
console.log("backend: /api/feedback POST called with data:", req.body);
const { id, name, feedback } = req.body;
// Basic validation
if (!id || !name || !feedback) {
return res.status(400).json({ error: "Missing required fields" });
}
else if (typeof id !== "number" || typeof name !== "string" || typeof feedback !== "string") {
return res.status(400).json({ error: "Invalid data types" });
}
else if (experiences.find(exp => exp.id === id) === undefined) {
return res.status(400).json({ error: "Experience with given ID does not exist" });
}
else if (name.trim() === "" || feedback.trim() === "") {
return res.status(400).json({ error: "Name and feedback cannot be empty" });
}
submitFeedback(id, name, feedback);
res.json({ result: "Feedback submitted successfully" });
});
// Start server
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
JSON file
JSON files can be used to store data persistently for the API to serve.
[
{
"id": 1,
"title": "Kapa Haka",
"description": "Traditional Māori performance",
"image": "kapa-haka.jpg"
},
{
"id": 2,
"title": "Wood Carving",
"description": "Workshop teaching authentic woodcarving techniques",
"image": "wood-carving.jpg"
},
{
"id": 3,
"title": "Hangi",
"description": "Māori earth oven cooked meal",
"image": "hangi.jpg"
}
]
[
{
"id": 1,
"name": "Jesse Schollitt",
"experienceId": 1,
"message": "Amazing performance and atmosphere"
},
{
"id": 2,
"name": "John Smith",
"experienceId": 2,
"message": "Enjoyed every minute! A must attend event"
},
{
"id": 3,
"name": "James McAvon",
"experienceId": 2,
"message": "Entertainment was okay, but way too long"
}
]
React + API
Backend interaction can be performed inside a React component.
API Demo Component
import { useState } from "react";
import ExperienceCard from "./ExperienceCard";
const API_URL = "http://localhost:3000/api/experiences";
function APIDemo() {
const [loading, setLoading] = useState(false);
const [data, setData] = useState([]);
async function fetchData() {
setLoading(true);
fetch(API_URL)
.then(res => res.json())
.then(resJson => setData(resJson))
.then(() => {setLoading(false)})
.catch(error => console.log(error));
}
return (
<div className="container mt-2">
<button
className="btn btn-primary mb-4"
onClick={fetchData}
>
Load Experiences
</button>
{ loading ? ( <h3>Loading...</h3> ) : (
data.length > 0 && (
<>
<p className="text-muted mb-2">
{data.length} data elements loaded.
</p>
{data.map((element, index) => (
<ExperienceCard key={index} details={element} />
))}
</>
)
) }
</div>
);
}
export default APIDemo;
ExperienceCard Component
import 'bootstrap/dist/css/bootstrap.min.css';
function ExperienceCard({ details }) {
return (
<div className="card mb-2">
<div className="card-body">
<div className="d-flex justify-content-between mb-1">
<span className="fw-medium">{details.title}</span>
</div>
<p className="card-text mb-2">{details.description}</p>
<img src={"https://picsum.photos/400?random=" + details.id} width={300}/>
</div>
</div>
);
}
export default ExperienceCard;