I set up the server using Express, including middleware for CORS, body parsing, and cookie parsing.
import express, { Express } from 'express';
import dotenv from 'dotenv';
import Products from './routes/product.routes';
import cors from 'cors';
import cookieParser from 'cookie-parser';
import bodyParser from 'body-parser';
const app: Express = express();
dotenv.config();
const port = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
app.use(bodyParser.json());
app.use(cookieParser(process.env.COOKIE_SECRET));
app.listen(port, () => {
console.log(`Server is running at http://localhost:${port}`);
});I designed the schema for the database to store products, users, reviews, carts, and sessions, ensuring relational integrity with foreign keys.
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
description TEXT,
image_path TEXT,
stock INTEGER,
category_id INTEGER,
price REAL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (category_id) REFERENCES categories(id)
);
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT,
email TEXT NOT NULL UNIQUE,
password TEXT,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);I implemented CRUD operations for products and carts, enabling interaction with the database.
export async function sqlDeleteProduct(productID: number) {
const deleteProduct = await db.prepare(`
DELETE FROM products WHERE id = ?
`).run(productID);
return deleteProduct;
}
app.delete('/product/:id', async (req: Request, res: Response) => {
const productId: number = parseInt(req.params.id);
const deleteProduct = await sqlDeleteProduct(productId);
res.send(`Product with ID ${productId} deleted successfully`);
});I added authentication functionality, including secure user registration, login, and session management using bcrypt and cookies.
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const users = await sqlFetchUserByEmail(email);
if (users.length > 0 && bcrypt.compareSync(password, users[0].password)) {
const sessionId = await sqlCreateSession(users[0].id, new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString());
res.cookie('sid', sessionId, { signed: true, httpOnly: true, maxAge: 604800000 });
res.status(200).json({ message: 'Login successful' });
} else {
res.status(401).json({ message: 'Invalid email or password' });
}
});I built dynamic and responsive user interfaces using React, including components for product display, search, and cart management.
const fetchProduct = async (prodId: number) => {
const productData = await singleProduct(prodId);
setProduct(productData[0]);
};
useEffect(() => {
fetchProduct(Number(id));
}, [id]);
return (
<div className="productContainer">
{product ? (
<>
<ImgDisplay imgurl={product.image_path} look="productImage" />
<p>{product.name}</p>
<p>{product.description}</p>
<p>{`Price: $${product.price}`}</p>
</>
) : (
<p>Loading...</p>
)}
<Button btnText="Add to basket" btnonClick={() => handleAddToBasket(Number(id))} />
</div>
);I implemented state management using the Context API to manage user state and shopping cart functionality.
import { createContext, useContext, useState } from 'react';
type UserContext = {
user: User | undefined;
setUser: React.Dispatch<React.SetStateAction<User | undefined>>;
};
const UserContext = createContext<UserContext | null>(null);
export default function UserContextProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | undefined>(defaultUser);
return <UserContext.Provider value={{ user, setUser }}>{children}</UserContext.Provider>;
}
export function useUserContext() {
const context = useContext(UserContext);
if (!context) throw new Error('useUserContext must be used within a UserContextProvider');
return context;
}Allows users to search for products by name or description and display the results.
import { SearchProducts } from '../../utils/endpoints';
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const response = await SearchProducts([userInput]);
setUser({ ...user, search: response });
setUserInput('');
};
return (
<form onSubmit={handleSubmit}>
<input value={userInput} onChange={(e) => setUserInput(e.target.value)} placeholder="Search" />
<Button btnText="Search" />
</form>
);I fetched and displayed product reviews and ratings.
const fetchProductReviews = async (prodId: number) => {
const reviewsData = await fetchReviews(prodId);
setReviews(reviewsData);
};
useEffect(() => {
fetchProductReviews(Number(id));
}, [id]);
return (
<div className="reviews">
{reviews.map((review) => (
<Review key={review.id} review={review} />
))}
</div>
);Understanding session management and secure password handling with bcrypt was challenging. I revisited concepts of token-based vs. session-based authentication to solidify my understanding.
Designing a relational database schema and ensuring data integrity with foreign keys required careful planning and adjustments. I revisited concepts of one-to-many and many-to-many relationships to optimize the database design.
Ensuring seamless communication between the React frontend and Express backend posed challenges, particularly with handling JSON data and API responses.
I encountered problems with CORS due to some computers resolving localhost as localhost and others as 127.0.0.1, causing inconsistent behavior. To resolve this, I removed the fixed IP in the CORS configuration, allowing for more flexibility and ensuring that the application worked consistently across different environments.
[Shaughn]
[What went well] can tell from commit times you did not do this all last minute which I like. Lots of small evidence rather than a few big ones is also very nice. [Even better if] Some learner outcomes refferfenced a bit more clearly than other. Also its a bit text relient describing whats going on in the code makes it clearer how it shows the learning outcome