LAB 3 — Collaborative Writing Tool (Part 1)
Accounts, Login, Projects, Membership, Shared Document (No realtime yet)
Summary (what you’re building)
You are building the first version of a collaborative writing tool:
- Users can register and log in
- After login, a user can:
- Create a writing project (they become the owner)
- Join a project using a join code
- See a list of projects they belong to
- Open a project and view the shared document
- Edit the document if the project is open
- The owner can:
- Close editing (project becomes read-only)
- Reopen editing
Important: In Lab 3, if two people edit at the same time, the “last save wins.” (Realtime comes in Lab 4.)
Learning goals
By the end of this lab, you can:
- Run a full-stack app on a remote Linux server using assigned ports
- Use MariaDB/MySQL from Node/Express
- Build a React app that uses React Router (first time!)
- Use JWT authentication (token-based login)
- Implement permissions: membership checks + owner-only actions
Server + Ports (Read This Carefully)
You will run everything on the Linux server:
- Server IP:
10.192.145.179 - Your instructor assigns you a base port between 4100–4200
Use this convention (recommended):
- API port = your base port
- Client port = base port + 1000
Example: base port 4123 → API on 4123, client on 5123.
You will access:
- Client:
http://10.192.145.179:5123 - API:
http://10.192.145.179:4123
If you can’t reach those ports from your laptop (fallback)
Use SSH port forwarding:
ssh -L 4123:localhost:4123 -L 5123:localhost:5123 yourUser@10.192.145.179
Then browse:
- Client:
http://localhost:5123 - API:
http://localhost:4123
Deliverables (what you submit)
- GitHub repo containing:
server/Express backendclient/React frontendserver/sql/001_init.sqlschema.env.examplefiles for both server and client- README with setup + demo steps
- README answers:
- Where are passwords stored and why is that safe?
- How does the frontend prove the user is logged in?
- How do you prevent non-owners from closing a project?
- Demo checklist in README (exact steps you can show a TA):
- register → login → create project → join project as another user → edit text → close → confirm editing blocked → reopen → confirm editing allowed
Part 0 — Quick Start Checklist (so you don’t get lost)
You will do these big steps in order:
- Connect to the Linux server (VS Code Remote SSH recommended)
- Copy
.env.exampleto.envfor server and client - Initialize your database schema
- Start backend (Express) on your API port
- Start frontend (Vite) on your client port
- Implement auth + routing + projects + document editing
Part 1 — Connect to the server (Remote SSH recommended)
1. Install VS Code Remote SSH
In VS Code:
- Extensions → install Remote – SSH (Microsoft)
2. Connect
Command Palette:
- Remote-SSH: Connect to Host…
- Enter:
yourUser@10.192.145.179
3. Open your lab folder on the server
Create a folder:
mkdir -p ~/comp318 cd ~/comp318
Clone your classroom repo there:
git clone <YOUR_REPO_URL> cd collab-writing-labs
Part 2 — Install dependencies
Open two terminals in VS Code.
Server dependencies
cd server npm install
Client dependencies
cd ../client npm install
Part 3 — Configure ports + env files (required)
You will have two .env files:
server/.envclient/.env
Server .env
cd server cp .env.example .env nano .env
Fill in values using your ports. Example uses base port 4123:
PORT=4123 CORS_ORIGIN=http://10.192.145.179:5123 DB_HOST=localhost DB_USER=<your_db_user> DB_PASSWORD=<your_db_password> DB_NAME=<your_db_name> JWT_SECRET=<choose-a-long-random-string>
Client .env
cd ../client cp .env.example .env nano .env
Example:
VITE_API_BASE=http://10.192.145.179:4123 VITE_PORT=5123
Part 4 — Initialize the database schema
From server/:
npm run db:init
Verify:
mysql -u <your_db_user> -p -h localhost <your_db_name> SHOW TABLES;
You should see tables like:
usersprojectsproject_membersproject_documents
Part 5 — Start the backend and frontend (remote ports)
Backend: must listen on all interfaces
In server/src/index.js, your listen should be:
app.listen(port, "0.0.0.0", () => console.log(`API listening on ${port}`));
Start backend:
cd server npm run dev
Test it:
curl http://localhost:4123/api/health
Frontend: must listen on all interfaces
In client/package.json, your dev script should use --host 0.0.0.0 and the env port:
"scripts": {
"dev": "vite --host 0.0.0.0 --port $VITE_PORT"
}
Start frontend:
cd client VITE_PORT=5123 npm run dev
Browse:
- Direct:
http://10.192.145.179:5123 - Or forwarded:
http://localhost:5123
Part 6 — React for the first time (mini tutorial you must use)
React mental model
- React UI is made of components (functions that return JSX).
- Components hold changing data using state (
useState). - Components run code after rendering using effects (
useEffect).
Example: controlled input (this is how forms work)
import { useState } from "react";
export default function ExampleForm() {
const [email, setEmail] = useState("");
return (
<div>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="Email"
/>
<p>You typed: {email}</p>
</div>
);
}
Part 7 — React Router (first exposure: you must implement routing)
You must use URLs:
/login/dashboard/projects/:projectId
7.1 Install router
cd client npm install react-router-dom
7.2 Wrap app in BrowserRouter
client/src/main.jsx (copy this):
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter } from "react-router-dom";
import App from "./App.jsx";
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
7.3 Define routes in App.jsx
client/src/App.jsx (copy this):
import React from "react";
import { Routes, Route, Navigate } from "react-router-dom";
import LoginPage from "./pages/LoginPage.jsx";
import DashboardPage from "./pages/DashboardPage.jsx";
import ProjectPage from "./pages/ProjectPage.jsx";
function isLoggedIn() {
return !!localStorage.getItem("token");
}
function RequireAuth({ children }) {
if (!isLoggedIn()) return <Navigate to="/login" replace />;
return children;
}
export default function App() {
return (
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/dashboard"
element={
<RequireAuth>
<DashboardPage />
</RequireAuth>
}
/>
<Route
path="/projects/:projectId"
element={
<RequireAuth>
<ProjectPage />
</RequireAuth>
}
/>
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
);
}
7.4 Navigate after login
In a component:
import { useNavigate } from "react-router-dom";
const navigate = useNavigate();
navigate("/dashboard");
7.5 Read a URL parameter
In ProjectPage.jsx:
import { useParams } from "react-router-dom";
const { projectId } = useParams();
Part 8 — Backend requirements (implementation tasks)
You will implement these endpoints. All routes except health/auth require JWT.
Auth
POST /api/auth/register→ store user with bcrypt password hashPOST /api/auth/login→ verify password, return JWT tokenGET /api/me→ returns user info if token valid
Password hashing (required)
import bcrypt from "bcrypt"; const hash = await bcrypt.hash(password, 12); const ok = await bcrypt.compare(password, user.password_hash);
JWT (required)
import jwt from "jsonwebtoken";
const token = jwt.sign({ userId: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: "7d" });
Projects
GET /api/projects→ list projects for current userPOST /api/projects→ create project (owner is current user)POST /api/projects/join→ join by join code
Document
GET /api/projects/:projectId/document→ returns project + document + isOwnerPUT /api/projects/:projectId/document→ updates doc if project open
Owner controls
POST /api/projects/:projectId/closePOST /api/projects/:projectId/reopen
Required security checks
Every project route must enforce:
- Membership check for non-owner actions
- Owner check for close/reopen
Part 9 — Frontend requirements (implementation tasks)
You must build these pages:
LoginPage
- A toggle: “Register” vs “Login”
- Register calls
/api/auth/register - Login calls
/api/auth/login - On login success:
localStorage.setItem("token", token)- navigate to
/dashboard
- Show error messages from server
DashboardPage
- Show:
- Create project form
- Join project form
- List of my projects (link to
/projects/:id)
- Project list includes status open/closed
ProjectPage
- Loads project doc with
/api/projects/:projectId/document - Shows:
- Project title
- Status open/closed
- Shared textarea
- Save button
- If closed:
- disable textarea and Save button
- If owner:
- show Close/Reopen button
Part 10 — Troubleshooting (common failures)
“My API calls fail in browser”
- Your server
CORS_ORIGINmust exactly match your client URL.
Example:- client:
http://10.192.145.179:5123 - server
.env:CORS_ORIGIN=http://10.192.145.179:5123
- client:
“I can’t access the Vite page”
- Make sure Vite is started with
--host 0.0.0.0 - If campus blocks ports, use SSH port forwarding fallback
