- Introduction
- Technologies
- Launch
- User Authentication
- Country indicators
- User’s searches in the database
- Database Schema
- License
- Developers
This is the backend server of the World Bank app that handles http requests from the client to access the database for information. We also have tests included.
This repository uses deno to run its files. For our database we use PostgreSQL to store world information and user data online using ElephantSQL.
There is another repository which is closely related and can be found here. It is recommended that you create a folder and clone both the frontend and backend repos into the same folder.
Start by forking this backend repository, and then cloning the repository into your local drive. Toggle into the directory, /WorldBackEnd, and run the following command into your terminal to initiate the backend server:
deno run --allow-net --allow-read --allow-write server.js
The frontend React application requires this backend server to be running to work correctly.
A session ID is created and stored in the browser’s cookies when the user registers/logs in. This keeps the user logged in as they navigate through the application. The V4 module is imported, and is used to generate a session ID, which is then inserted into the users database.
const sessionId = v4.generate();
await userDatabase.queryArray({
text: `INSERT INTO sessions (uuid, user_id, created_at) VALUES ($sessionId, $user_id, current_timestamp)`,
args: { sessionId: sessionId, user_id: user_id },
});
The cookies are set here and then passed to the browser. The expiration date for the cookie is set for 24 hours.
await server.setCookie({
name: "sessionId",
value: sessionId,
expires: expiryDate,
path: "/",
});
await server.setCookie({
name: "user_id",
value: user_id,
expires: expiryDate,
path: "/",
});
await server.setCookie({
name: "email",
value: userRows.email,
expires: expiryDate,
path: "/",
});
async function findCurrentUserId(email) {
const checkEmail = await userDatabase.queryObject({
text: "SELECT * FROM users WHERE email = $email",
args: { email: email },
});
const [checkEmailRows] = checkEmail.rows;
return checkEmailRows.id;
}
A POST request from the registration page (localhost:3000/register) is made when a new user submits their credentials for the first time. This takes a username and password in the body of the request.
A new salt is generated for each registration and placed into the password. This salted password is then hashed using the bcrypt library’s hashing function:
const salt = await bcrypt.genSalt(8);
const passwordEncrypted = await bcrypt.hash(password, salt);
Before the user is registered, a check takes place to see if a user already exists with the same username provided. If the username already exists in the database, an error is returned, preventing the user from registering with those credentials. Otherwise, the user’s credentials are stored into the database.
if (theEmail) {
return server.json({ error: "User already exists" }, 400);
} else {
const query = `INSERT INTO users (email, password, salt, created_at, updated_at)
VALUES ($username, $passwordEncrypted, $salt, current_timestamp, current_timestamp)`;
await userDatabase.queryArray({
text: query,
args: {
username: username,
passwordEncrypted: passwordEncrypted,
salt: salt,
},
});
Afterwards, a session is created for the user and they are redirected to the search page.
const user_id = await findCurrentUserId(username);
await createSession(server, user_id);
When the user hits the submit button on the log-in page, a POST request is made to server.js, where the checkUserLogin handle is triggered. The username and password are sent in the body of the request.
const { email, password } = await server.body;
const checkEmail = await userDatabase.queryObject({
text: "SELECT * FROM users WHERE email = $email",
args: { email: email },
});
A query is made to the database to check if the user exists, otherwise an error message is returned. If the user exists, a session is created for them, and they are redirected to the search page.
if (checkEmailRows) {
if (await bcrypt.compare(password, checkEmailRows.password)) {
await createSession(server, checkEmailRows.id);
return server.json(checkEmailRows, 200);
} else {
server.json({ error: "Incorrect password" }, 400);
}
} else {
server.json({ error: "User not found." }, 404);
}
You need to be logged in to be able to search for country indicators.
The country endpoint is required, and will give information about all available indicators for the country entered.
/{country}
Ex. /Afghanistan
You can input indicators and timeframes as query parameters to restrict the returned information. Without any the default parameters are
Indicator = ’’
startYear = 1960
endYear = 2015
There’s only information from 1960 to 2015 so those are the default parameters.
After the country endpoint you can add an indicator query to find indicator information that will include that indicator.
/{country}?indicator={indicator}
Ex. /Albania?indicator=urban%20population
You can also add queries to choose a specific timeframe to get information from.
/{country}?startYear={startYear}&endYear={endYear}
Ex. /Argentina?startYear=1990
/Australia?endYear=2007
/Bulgaria?startYear=1995&endYear=2010
You can combine all the queries as well
Ex. /Botswana?indicator=tariff&startYear=2005&endYear=2012
Everytime a user searches for information, it’s stored into a table that stores the user’s id and search parameters that can be accessed and searched again.
The user’s id is passed in the URL of the request, which is then used to query all of their searches from the searches table.
const { user_id } = server.params;
const response = await userDatabase.queryObject({
text: "SELECT created_at, country, indicator, start_year, end_year FROM searches WHERE user_id = $user_id",
args: { user_id },
});
The license for this project can be found here
The Developers that worked on this project are:
Project Manager & Engineer: Mariam Raashid
Architect & Engineer: Tom Waghorn
Quality Assurance & Engineer: Fahmidul Haque
Quality Assurance & Engineer: Kishor Shris