Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion shatter-backend/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
// Express framework gives us fucnctions to creeate web server, handle routes, process HTTP req and send responses
import express from 'express';
import userRoutes from './routes/user_route.ts';
import userRoutes from './routes/user_route.ts'; // these routes define how to handel requests to /api/users

// Creating express application as a single "app" object, started in server.ts
// This object represents entire web server and will be used to:
// register Middleware, define routes ....
const app = express();

// Middleware setup
// responsible for parsing incoming JSON request bodies, making req.body usable in controllers
app.use(express.json());

// Defining routes
// a simple route that sends back plain text "Hello" when anyone visits the app in browser
app.get('/', (_req, res) => {
res.send('Hello');
});

// Mounts the user routes under the path 'api/users'
// any routes defined in user_route.ts will be accessible with URLs starting with /api/users
app.use('/api/users', userRoutes);


// Export the configured Express app, so that server.ts can import it
// Keeping app setup separate from server startup makes the code modular and testable
export default app;
57 changes: 43 additions & 14 deletions shatter-backend/src/controllers/user_controller.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,50 @@
// import req and res types for type safety
import { Request, Response } from 'express';
import { User } from '../models/user_model.ts'; // imports user model created with mongoose

let users = [
{ id: 1, name: 'Minh', email: 'minh.le4@ucalgary.ca' }
];
// controller: GET /api/users
// This function handles GET reqs to /api/users
// It fetches all users from MongoDB and sends them as json

export const getUsers = (_req: Request, res: Response) => {
res.json(users);
export const getUsers = async (_req: Request, res: Response) => {
try {
// retrieves all docs from "users" collection
const users = await User.find().lean(); // .lean() returns plain JS objects instead of Mongoose docs, may change incase we need extra model methods later
res.json(users); // sends list of users back as JSON response
} catch (err) { // log the error if something goes wrong
console.error('GET /api/users error:', err);
res.status(500).json({ error: 'Failed to fetch users' });
}
};

export const createUser = (req: Request, res: Response) => {
const { name, email } = req.body;

if (!name || !email) {
return res.status(400).json({ error: 'name and email required' });
}

const newUser = { id: users.length + 1, name, email };
users.push(newUser);
res.status(201).json(newUser);
// controller: POST /api/users
// reads data from req body, vailidates it and creates a new user
export const createUser = async (req: Request, res: Response) => {
try {
// Destructure the req body sent by the client
// The ?? {} ensures we don't get error if req.body is undefined
const { name, email } = req.body ?? {};

// Basic validation to ensure both name and email are provided
// if not respond with bad request and stop further processes
if (!name || !email) {
return res.status(400).json({ error: 'name and email required' });
}

// create a new user doc in DB using Mongoose's .create()
const user = await User.create({ name, email });
// respond with "created" and send back created user as JSON
res.status(201).json(user);
} catch(err: any) {
// Handle duplicate email error
// Mongo DB rejects duplicat value since we have email marked as 'unique'
if (err?.code === 11000) {
return res.status(409).json({ error: 'email already exists' });
}

// for all other errors, log them and return generic 500 response
console.error('POST /api/users error:', err);
res.status(500).json({error: 'Failed to create user' });
}
};
Empty file.
47 changes: 47 additions & 0 deletions shatter-backend/src/models/user_model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Import Schema and model from the Mongoose library.
// - Schema: defines the structure and rules for documents in a collection (like a blueprint).
// - model: creates a model (class) that we use in code to read/write those documents.
import { Schema, model } from 'mongoose';

// define TS interface for type safety
// This helps IDE and compiler know what fields exist on a User

export interface IUser {
name: string;
email: string;
}

// Create the Mongoose Schema (the database blueprint)

// A Schema tells Mongoose what fields each document should have
// and what rules apply to those fields.
const UserSchema = new Schema<IUser>(
{
name: {
type: String,
required: true, // field is mandatory; Mongoose will throw error if missing
trim: true // removes extra space at start and end
},
email: {
type: String,
required: true,
trim: true,
lowercase: true, // converts all emails to lowercase before saving for consistency
unique: true // enforce uniqueness, error 11000 if duplicate is detected
}
},
{
// timestamps: true automatically adds two fields to each document:
// - createdAt: Date when the document was first created
// - updatedAt: Date when the document was last modified
timestamps: true
}
);


// create and export mongoose model
// model is simply a wrapper around schema that gives access to MongoDB opeprations

// "User" is the model name
// Mongoose will automatically use "users" as the collection name in MongoDB
export const User = model<IUser>('User', UserSchema);
13 changes: 10 additions & 3 deletions shatter-backend/src/routes/user_route.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
// router is like a mini-express app that allows grouping of related routes together
import { Router } from 'express';
import { getUsers, createUser } from '../controllers/user_controller.ts';
// Importing controller functions that handle logic for each route
// These function define what happens when a req is received

// creating new router instance
const router = Router();

router.get('/', getUsers);
router.post('/', createUser);
// Defining routes for the /api/users path
router.get('/', getUsers); // when GET req is made, run getUsers func
router.post('/', createUser); // when POST req is made, run creatUser func

export default router;
// Export the router so it can be used in app.ts
// app.ts imports router and mounts it under '/api/users'
export default router;
35 changes: 30 additions & 5 deletions shatter-backend/src/server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,32 @@
import app from './app.ts';
// This is the main entry point that starts the application

const PORT = 4000;
import 'dotenv/config'; // loads .env file and populates process.env (the node.js object)
import mongoose from 'mongoose'; // mongoose is an Object Data Modelling (ODM) livrary for MongoDB
import app from './app.ts'; // Import the express app

app.listen(PORT, () => {
console.log(`Server running on http://localhost:${PORT}`);
});

// config

const PORT = process.env.PORT ? Number(process.env.PORT) : 4000; // use defined PORT in .env if present , otherwise default to 400
const MONGODB_URI = process.env.MONGO_URI; // read the mongoDB connection from env variables

// Start up function
async function start() {
try {
if (!MONGODB_URI) { // check that MOGO URI is provided in .env
throw new Error('MONGODB_URI is not set');
}
await mongoose.connect(MONGODB_URI); // this returns a promise, so we 'await' until it's successfully connected
console.log('Successfully connected to MongoDB');

// start listening for incoming HTTP requests on chosen port
app.listen(PORT, () => {
console.log('Server running on http://localhost:${PORT}');
});
} catch (err) { // if connection goes wrong, log the error
console.error('Failed to start server:', err);
process.exit(1); // code 1 indicates failure
}
}

start();
Loading