Previously, I was documenting my ups and downs of my demo hiring app, but I thought it would be easier to build the whole thing and show you guys how I did it.
I split the project into 2 parts. The first app is for employers looking for candidates and managing them through a Trello board replica I created within the app. The second app focuses on the candidates looking for jobs, viewing company pages, and applying for jobs.
1st app - Hire+
2nd app - Hire+ Employers
Both apps have similar design styles and views in order to keep things simple.
Let's dive right in. Here's how I built the 1st app (Hire+) starting from backend. I'm using firebase as my backend. I created my project with the authentication and database portions. Here's how it looks. (both apps are using this database)
- Auth providers I have enabled for this project
- Current test users I was testing with
- Database structure for the whole app (yup, only 3 collections to get everything working)
DB structure:
- Employers are a collection of companies
{
id: xxxxxxxxxx,
company: Goodcorp,
companyUrl: www.Goodcorp.com,
companyDescription: lorem ipsum.....,
email: goodcorp@mail.com,
isHiring: true,
companySize: 1-50,
jobs: [Jobs],
}
- Employees are a collection of candidates
{
id: 'xxxxxxxx',
name: 'james',
email: 'james@mail.com',
title: 'Front-end developer',
isForHire: true,
websiteUrl: 'www.me.com',
githubUrl: 'www.james@github.com'
skills: [],
summary: 'lorem ipsum',
projects: [],
experience: [],
}
- Jobs are a collection of jobs (the company posts)
{
id: 'xxxxxxxx',
position: 'Web developer',
location: 'remote',
salary: '70k',
datePosted: 'Jun 1,2022',
jobType: 'full-time',
applyUrl: 'www.mycompany.com',
description: 'lorem ipsum',
company name: 'a company name',
}
Firebase.utils.ts file
import { initializeApp } from 'firebase/app';
// methods used to handle retrieving, updating, and adding data in DB.
import { getFirestore, doc, getDoc, setDoc, QueryDocumentSnapshot, collection, query, getDocs, where, updateDoc, arrayUnion } from 'firebase/firestore';
// methods used to handle sign in, sign up, sign-out, sign-in with google, and anything profile related.
import { getAuth, signInWithPopup, GoogleAuthProvider, signInWithEmailAndPassword, createUserWithEmailAndPassword, User, NextOrObserver, onAuthStateChanged, updateProfile, signOut } from 'firebase/auth';
// Data types I'm using within the DB
import { ProfileData, UpdatedFields } from '../../app/features/profile/profileTypes';
import { JobData } from '../../app/features/job/jobTypes';
import { SignUpFields } from '../../app/features/user/userTypes';
import { CompanyData } from '../../app/features/company/companyTypes';
// connecting firebase to project
const firebaseConfig = {
apiKey: 'AIzaSyCg113wgJGlfL1T8B7SwVSO6a-UezmyAas',
authDomain: 'hireplus-268ed.firebaseapp.com',
projectId: 'hireplus-268ed',
storageBucket: 'hireplus-268ed.appspot.com',
messagingSenderId: '884090567451',
appId: '1:884090567451:web:0556a5662a9b0d368ff1be',
};
// Initialize Firebase
const firebaseApp = initializeApp(firebaseConfig);
// setup popup for google sign-in
const googleProvider = new GoogleAuthProvider();
googleProvider.setCustomParameters({
prompt: 'select_account',
});
// Firebase setup
export const auth = getAuth();
export const db = getFirestore(firebaseApp);
Helper functions for Auth
Still inside firebase.utils.ts
file
// Sign in with google func
export const signInWithGooglePopup = async () => {
const { user } = await signInWithPopup(auth, googleProvider);
await createUserDocument(user);
};
// sign up with email and password
export const signUpEmailAndPassword = async (formFields: SignUpFields) => {
const { email, password, displayName } = formFields;
const { user } = await createUserWithEmailAndPassword(auth, email, password);
await updateProfile(user, { displayName });
await createUserDocument(user);
return user;
};
// Sign in with email and password
export const signInEmailAndPassword = async (
email: string,
password: string
) => {
if (!email || !password) return;
const userDocRef = collection(db, 'employees');
const doc = query(userDocRef, where('email', '==', email));
const docSnapshot = await getDocs(doc);
if (docSnapshot.empty) {
return;
} else {
return await signInWithEmailAndPassword(auth, email, password);
}
};
// create db from signed in user
export const createUserDocument = async (authUser: User): Promise<void | QueryDocumentSnapshot<ProfileData>> => {
if (!authUser) return;
const userDocRef = doc(db, 'employees', authUser.uid);
const userSnapShot = await getDoc(userDocRef);
// if user doc doesn't exist, will create one in collection
if (!userSnapShot.exists()) {
const { email, displayName } = authUser;
const createdAt = new Date();
try {
await setDoc(userDocRef, {
id: authUser.uid,
email,
name: displayName,
createdAt,
headline: '',
isForHire: false,
websiteURL: '',
skills: [],
summary: '',
projects: [],
experience: [],
});
} catch (error) {
console.log('get user auth and create doc', error);
}
return userSnapShot as QueryDocumentSnapshot<ProfileData>;
}
};
export const logoutUser = async () => await signOut(auth);
signInWithGooglePopup() - Sign-in a user with google account
signUpEmailAndPassword() - Gets the form data from frontend and signups user using firebase func createUserWithEmailAndPassword
. It returns a user, and we update the profile, so the displayName
will be what it is from form data.
Once user signed up, we use that info to create the user in the DB with the createUserDocument
func. It will create the user in the employees collection. Lastly, return the user to make use of it later.
signInEmailAndPassword() - I check to see if the user's email can be found in the employees collection. If not, it means the user didn't sign up first. If yes, then they already signed up. Now they can sign in.
createUserDocument() - This func does all the heavy lifting. It takes in the signed in user and creates a doc in the employees collection. If user doc doesn't exist, it will create one in collection.
The id
of each doc will be linked to the signed in user id
. When user is created in employees collection, it will have default data, seen in the setDoc
method. Lastly, it casts that data as the ProfileData
data-type and returns it for later use.
logoutUser() - signs out user
onAuthStateChangedListener() - Keeps track of the current user, if they're signed or out.
That's all I needed to get all the authentication working.
Helper functions for DB
Still inside firebase.utils.ts
file.
I split it up in 3 sections (Profile, Jobs, Company)
Profile
export const getProfile = async (id: string): Promise<ProfileData[]> => {
const collectionRef = collection(db, 'employees');
const q = query(collectionRef, where('id', '==', id));
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map((docSnapshot) => {
return docSnapshot.data() as ProfileData;
});
};
export const updateUserProfileById = async (data: UpdatedFields) => {
const {id, headline, summary, skills, projects, experience,
isForHire,
websiteURL,
} = data;
const docRef = doc(db, 'employees', id);
const currentDocSnap = await getDoc(docRef);
await updateDoc(docRef, {
isForHire: isForHire ? isForHire : currentDocSnap.data().isForHire,
websiteURL: websiteURL ? websiteURL : currentDocSnap.data().websiteURL,
headline: headline ? headline : currentDocSnap.data().headline,
summary: summary ? summary : currentDocSnap.data().summary,
skills: arrayUnion(...skills),
projects: arrayUnion(...projects),
experience: arrayUnion(...experience),
}).then(() => {
console.log('updated successfully');
});
};
getProfile() - Get a user from employees
collection. I check if the id
matches an employee id
from employees
collection. I cast that data as a ProfileData
data type and return it for later use.
updateUserProfileById() - update a user from employees
collection. I check if the id
matches an employee id
from employees
collection. I get that user doc and update it's fields with updateDoc
. If the fields hasn't changed, or the value is empty, those fields will have current DB value. Otherwise, it updates to new value.
Jobs
export const getJobs = async (): Promise<JobData[]> => {
const querySnapshot = await getDocs(collection(db, 'jobs'));
return querySnapshot.docs.map((doc) => {
return doc.data() as JobData;
});
};
export const getJobById = async (id: string): Promise<JobData[]> => {
const collectionRef = collection(db, 'jobs');
const q = query(collectionRef, where('id', '==', id));
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map((docSnapshot) => {
return docSnapshot.data() as JobData;
});
};
getJobs() - Get the jobs from jobs collection and return that data(array of jobs) as JobData data type. This func is assuming the employers are adding jobs to the jobs collection.
getJobById(id) - Get a job by id
, check if id
matches in jobs
collection. If so, return that data as JobData
data type.
Company
export const getCompanyById = async (id: string) => {
const collectionRef = collection(db, 'employers');
const q = query(collectionRef, where('id', '==', id));
const querySnapshot = await getDocs(q);
return querySnapshot.docs.map((docSnapshot) => {
return docSnapshot.data() as CompanyData;
});
};
getCompanyById(id) - gets the company(employer) by id
. Checks if id
matches in employers
collection, then returns data as CompanyData
data-type.
That's all the functions I use for the backend, the rest is just calling them in the frontend when appropriate. Stay tuned! github
Top comments (0)