Features β’ Installation β’ Updating β’ APIs β’ Contributing β’ Credits β’ License
- Users can signup and login to their accounts
- Companies can be created using a form on the home page
- A user can search all saved companies by company name, location, role, etc.
This app is a monorepo that houses both the frontend and backend of Hitlist Pro. To get the app running you will need to run the backend and frontend independantly. There are some pre-requisites for this project:
Node.js (at least version 18 or higher) Vite NVM (this is optional, but will be helpful. You can also use NPM)-
Clone the repository onto your machine.
-
Navigate to
./frontend. -
Run
npm install. If you have an issue downloading the project dependencies, you may have an outdated node version. If you are using nvm, runnvm use 18.18.0. The required version of the project may change. Check the terminal to determine which version nvm should use. -
Run
npm run dev. This will start the frontend. -
Navigate to
http://localhost:5174/
To run the front end from the backend folder:
-
In frontend folder:
npm run buildfirst to create minimized dist directory -
In frontend folder:
cp -r dist ../backendto copy minimized dist folder from the frontend into backend -
Still In the frontend folder:
npm run dev -
Then in the backend folder run
npm run dev
-
Run
npm i. If you are using nvm make sure to runnpm use [required node version for project] -
Navigate to
./backend -
Run
npm run start -
Ensure the backend is at
http://localhost:3001/
This project used workspaces to manage dependencies within its monorepo. You can read more about how workspaces work here in NPM's documentation. The active workspaces in this project are listed within ./package.json and ./package-lock.json.
To create a new workspace you can run npm init -w [the path of your workspace]. I do not reccommend updating these files manually since a typo could really ruin your day and introduce bugs.
When you are creating a PR for the project, make sure to fill out the PR template. The template exist within the ./github folder at the root of the project. If you make changes to this template, like all other changes, be sure to create a Pull Request. For more information on the reasoning behind PR templates, see This PR Template guide or checkout github's documentation. All PRs should be comparing the main branch to your feature branch. Here is an example of a PR description using the template: Documentation Initial Readme
Squash. After your PR is approved by our DEV OPs lead, you will want to squash merge your changes. Your commit history leading up to your PR is not needed for posterity. If you don't squash then this history will end up on the main branch. Feel free to keep that record on your machine or on that specific branch.
So how and why do I squash merge my changes?
Squash merging is usually an option listed below a PR after it is approved.
That button gives github permission to Squash the git history associated with the feature branch that you are tying to merge into main.
That way the commit history under your feature branch is mot merged into the history of the main branch which can kind of clutter the projectβs full history over time. See HitList Pro Commit History.
If every developer who pushed the changes to the main branch retained their commit history, this page would grow exponentially over time. This would make it more difficult to pin down specific feature changes and would add possibly unnecessary commits to our git history. A pro on the other hand is that the project would retain a high level of detail about all the commits that have made up the project. There are pros and cons to squashing.
If you want to squash merge without github, you can do a git squash locally before or after your PR is approved. After you squash (and rebase if you need to). Then push your changes and merger them to main. Here is an article that explains more about git squash and how to use it: Free code camp article on squash commits
Connect to the API using Postman or Insominia. The API lives at
http://localhost:3001 when the server is running.
GET: /signup - Action: To create an account for the user.
| Status | Response |
|---|---|
201 |
{ "user": {}, "message": "User registered successfully", "token": "string" } |
400 |
{ "message": "Email already in use" } |
500 |
{ "message": "Server error", "error": "string" } |
GET: /login - Action: To sign the user into the app.
| Status | Response |
|---|---|
200 |
{ "message": "Login successful", "token": "string", "user": {} } |
400 |
{ "message": "Invalid email or password" } |
500 |
{ "message": "Server error", "error": "string" } |
GET: /companies - Action: To retrieve a list of companies and applications saved by the user.
| Status | Response |
|---|---|
200 |
{ "companies": [{ "name": "string", "status": "string", "applicationUrl": "string", "notes": "string", "pointOfContacts": ["string"] }] } |
500 |
{ "error": "Internal Server Error" } |
PUT: /companies/:id - Action: To update the details of an existing company.
Request Params: { "id": "string" }
| Status | Response |
|---|---|
200 |
{ "companies": [{ "name": "string", "status": "string", "applicationUrl": "string", "notes": "string", "pointOfContacts": ["string"] }] } |
500 |
{ "error": "Internal Server Error" } |
GET: /all-contacts - Action: To retrieve an array of all the user's contacts.
| Status | Response |
|---|---|
200 |
{ "allContacts": ["string"] } |
500 |
{ "error": "Internal Server Error" } |
const mongoose = require('mongoose');
const contactSchema = new mongoose.Schema({
role: String,
email: String,
linkedIn: String,
company: String, // Deprecated: This is being replaced by the reference to Company model below.
company: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Company' // References the 'Company' model, establishing a relationship between Contact and Company
},
lastContactDate: Date,
});
const ContactModel = mongoose.model('Contact', contactSchema);
module.exports = ContactModel;- role: The role of the contact (e.g., Manager, Developer).
- email: The contact's email address.
- linkedIn: The contact's LinkedIn profile.
- company: Deprecated. Previously used as a string field, now replaced with an ObjectId reference to the Company model.
- company: The ObjectId referencing a company document from the Company model. This creates a relationship between the Contact and Company models.
- lastContactDate: The most recent date when the contact was interacted with.
const mongoose = require('mongoose');
const companySchema = new mongoose.Schema({
name: String,
status: String,
applicationUrl: String,
notes: String,
pointOfContacts: [String], // Deprecated: This is replaced by a reference to the Contact model below.
pointOfContacts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Contact' // References the 'Contact' model, establishing a relationship between Company and Contact
}],
});
const CompanyModel = mongoose.model('Company', companySchema);
module.exports = CompanyModel;- role: The role of the contact (e.g., Manager, Developer).
- email: The contact's email address.
- linkedIn: The contact's LinkedIn profile.
- company: Deprecated. Previously used as a string field, now replaced with an ObjectId reference to the Company model.
- company: The ObjectId referencing a company document from the Company model. This creates a relationship between the Contact and Company models.
- lastContactDate: The most recent date when the contact was interacted with.
The Contact and Company schemas are related through the company field in the Contact schema and the pointOfContacts field in the Company schema:
- The
companyfield in the Contact schema references the_idof a Company, establishing a connection between a contact and the company they work for or are associated with. - The
pointOfContactsfield in the Company schema is an array ofObjectIdreferences to the Contact model, which holds the contacts associated with a specific company.
This relationship allows you to easily retrieve information about a contact's associated company or find all contacts linked to a specific company.
- When querying for a Contact, you can populate the
companyfield to get detailed information about the company the contact works for. - When querying for a Company, you can populate the
pointOfContactsfield to retrieve all contacts (e.g., employees or representatives) associated with that company.
Let's use the supertest package to help us write our tests for testing the API.
MONGO_URI='your_mongo_uri_here'
TEST_MONGO_URI='your_test_mongo_uri_here'
TEST_PORT=4000
npm install --save-dev supertest "scripts": {
"start": "cross-env NODE_ENV=production node server.js",
"build-prod": "cd ../frontend/ && npm run build && cp -r dist ../backend/",
"dev": "cross-env NODE_ENV=development node --watch server.js",
"lint": "eslint --fix .",
"format": "npx prettier --write \"**/*.{ts,js,md}\"",
"check": "npx prettier --check \"**/*.{ts,js,md}\"",
"test": "cross-env NODE_ENV=test node --test"
},Create a test file inside backend/tests/, e.g., sample-test.js:
// Importing the test function and 'after' hook from 'node:test'
const { test, after } = require('node:test');
// Importing supertest, which allows us to send HTTP requests to our app
const request = require('supertest');
// Importing mongoose for database connection management
const mongoose = require('mongoose');
// Importing the Express app from the server file
const app = require('../server');
// This function runs after all tests are done
after(async () => {
// Close the MongoDB connection to avoid open connections
await mongoose.connection.close();
});
test('GET /companies/ should return JSON', async () => {
await request(app) // Send a GET request to the endpoint
.get('/companies/')
.expect(200) // Expect HTTP status 200 (OK)
.expect('Content-Type', /application\/json/); // Expect JSON response
});What it does:
- Sends an HTTP
GETrequest to/companies/usingsupertest. - Verifies that the server responds with status
200. - Ensures that the
Content-Typeof the response is JSON (application/json).
cd .. Sprint3-HitlistCohort and find what test file you want to test and then run:
NODE_ENV=test node --test backend/tests/sample-test.jsThis guide helps you get started with Cypress to run end-to-end tests on your Vite + Express application.
Install Cypress as a development dependency:
npm install --save-dev cypressThen, install the Cypress binary:
npx cypress installStart your frontend and backend servers in separate terminals:
# Terminal 1 - Start Vite dev server
npm run dev
# Terminal 2 - Start Express backend
node server.js or npm run devThen, open the Cypress test runner:
npx cypress openThis will open the Cypress UI where you can select and run tests interactively.
After first run, Cypress creates the following structure:
cypress/
e2e/
login.cy.js # Your test file
support/
fixtures/
...
cypress.config.js # Cypress config file
Create a test file in cypress/e2e/login.cy.js:
describe('Login and Logout Flow', () => {
it('logs in and logs out successfully', () => {
cy.visit('http://localhost:5173'); // Vite app
cy.get('input[name="email"]').type('testuser@example.com');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.contains('Welcome, Test User');
cy.get('button#logout').click();
cy.contains('Login');
});
});To run Cypress tests in the terminal without opening the UI:
npx cypress runThis will execute tests in headless mode using Electron by default.
- Make sure your test data (e.g.
testuser@example.com) exists in your backend. - You can use
cy.request()to seed data or log in via API. - Cypress automatically waits for elements β no need to use timeouts manually.
This guide will help you set up and use Jest for unit testing your JavaScript or React codebase.
Install Jest as a development dependency:
npm install --save-dev jestIf you're using Babel (e.g. with React or modern JS syntax), install these too:
npm install --save-dev @babel/preset-env @babel/preset-react babel-jestCreate a babel.config.js file:
module.exports = {
presets: ['@babel/preset-env', '@babel/preset-react'],
};Add this to your package.json:
"scripts": {
"test": "jest"
}Or create a jest.config.js:
module.exports = {
testEnvironment: 'jsdom',
moduleFileExtensions: ['js', 'jsx'],
transform: {
'^.+\\.(js|jsx)$': 'babel-jest',
},
};Create a test file like sum.test.js:
function sum(a, b) {
return a + b;
}
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});Run the test:
npm testExample Button.test.jsx:
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import Button from './Button';
test('calls onClick when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click Me</Button>);
fireEvent.click(screen.getByText('Click Me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});Install testing library if needed:
npm install --save-dev @testing-library/react @testing-library/jest-domRun Jest in watch mode:
npm test -- --watch- Use
.test.jsor.spec.jssuffixes for test files. - Mock functions with
jest.fn(). - Use
describe()blocks to group related tests. - Snapshot testing is supported with
expect(component).toMatchSnapshot().
npm test -- --coverageThis generates a coverage/ folder with HTML reports.
Happy testing! β¨
Should look like this:
Before you begin, ensure you have:
- A Fly.io account (Sign up here)
- Fly CLI installed (Installation guide)
Authenticate your Fly.io account:
fly auth loginNavigate to the backend directory and run:
fly launch- Choose hitlist as the app name or create a new one.
- Select bos (Boston) as the deployment region.
- This command generates a
fly.tomlconfiguration file.
To deploy, run:
fly deployOnce deployed, Fly.io will provide a public URL where the backend is accessible.
View Logs:
fly logsRestart the App:
fly restartFor the latest installation instructions, visit the official guide:
π Flyctl Installation Guide








