A real-time privacy-first social media platform leveraging feature-rich direct messaging text channels. Built as part of the course project for COSC 310 at UBC.
As we begin to integrate database functionality across our components, we will need to process all user input with our central database. As all user-input is received on the client-side, you will need to process that data in the server (Node.js). To do this, you cannot directly interact with the database in the onSubmit method for your forms, but will need to send an API request to the API endpoint that is responsible for handling that event.
You will need to define REST API endpoints under the pages/api directory where for every form you will write a corresponding handler that intakes that data and sends it to MongoDB and then returns some data back to that component in the frontend. In summary, the process is as follows:
In your frontend component, write an HTTP request to the corresponding API endpoint in the server that is designed to handle that event. This is done in every onSubmit request or wherever necessary.
Write the API endpoint that will handle that request. Depending on the process executed, you will need to return some data back to the original frontend component.
Receive all status updates from the API endpoint in the frontend component and handle them accordingly.
Example -- Login Page
Frontend Login Form Component
/**
* The Login component provides an entry point to the application, displaying a form
* that accepts a username and password. Upon form submission, it sends these credentials
* to the /api/login endpoint for verification and handles the response to either
* redirect the user to the main application page or display error messages.
*/
export function Login() {
const router = useRouter(); // To handle page redirection after the user logs in
// State hooks for form data and validation errors
const [formData, setFormData] = useState<{userName: string, password: string}>({
userName: "",
password: "",
});
// To facilitate client-side validation
const [usernameError, setUsernameError] = useState(''); // The message for an incorrect username
const [passwordError, setPasswordError] = useState(''); // The message for a correct username but incorrect password
/**
* Handles form submission, sending the login request to the server and processing
* the response to either proceed to the application or show error messages.
*
* @param event - The form submission event
*/
const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
const response = await fetch('/api/login', { // Establishing a promise
method: 'POST', // As we are dealing with authentication, this is the most appropriate method
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData), // The data that is being sent
});
const data = await response.json(); // Awaiting the resolution of the promise
if (response.ok) { // If the response was successful
router.push('/accord'); // Redirect the user to the main application page
} else {
if (data.error === 'Invalid username') {
setUsernameError('Invalid username. Please try again.'); // Client-side validation
} else if (data.error === 'Invalid password') {
setPasswordError('Invalid password. Please try again.'); // Client-side validation
}
}
};
// more code follows
}
Backend API endpoint
/**
* Handles the POST request for user login, validating the user credentials against the database.
* Responds with a success message and status code 200 if the credentials are valid, or an error
* message and status code 401 for invalid credentials, and 500 for any internal server errors.
*
* @param req - The incoming Next.js API request object, containing the username and password.
* @param res - The outgoing Next.js API response object used to send back the result.
*/
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const { userName, password } = req.body; // Intaking the data that has been sent from the client-side
let client: MongoClient | null = null; // We need to assign something to the client so TypeScript is aware that it can be null if the connection fails
try {
client = new MongoClient(getMongoDbUri());
await client.connect();
const db = client.db('Accord');
const accountsCollection = db.collection("Accounts");
// Querying the database by the username we received
const user = await accountsCollection.findOne({ userName: userName }); // IMPORTANT: The findOne method returns a promise, so we need to await the resolution of the promise first
if (user && user.password === password) {
return res.status(200).json({ message: 'Login successful' });
} else {
if (!user) {
return res.status(401).json({ error: 'Invalid username' });
} else {
return res.status(401).json({ error: 'Invalid password' });
}
}
} catch (error) {
console.error(error);
return res.status(500).json({ error: 'Internal server error' });
} finally {
if (client) {
await client.close();
}
}
} else {
// Handle any requests other than POST
res.setHeader('Allow', ['POST']);
res.status(405).end(`Method ${req.method} Not Allowed`);
}
}
As we begin to integrate database functionality across our components, we will need to process all user input with our central database. As all user-input is received on the client-side, you will need to process that data in the server (Node.js). To do this, you cannot directly interact with the database in the
onSubmit
method for your forms, but will need to send an API request to the API endpoint that is responsible for handling that event.You will need to define REST API endpoints under the
pages/api
directory where for every form you will write a corresponding handler that intakes that data and sends it to MongoDB and then returns some data back to that component in the frontend. In summary, the process is as follows:onSubmit
request or wherever necessary.Example -- Login Page
Frontend Login Form Component
Backend API endpoint