DEV Community

Cover image for I finished my demo hiring platform app: Hire+Plus! Here's how I built it (Backend)
AjeaS
AjeaS

Posted on

I finished my demo hiring platform app: Hire+Plus! Here's how I built it (Backend)

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 enabled

test users for auth

Database structure

  1. Auth providers I have enabled for this project
  2. Current test users I was testing with
  3. 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],
}
Enter fullscreen mode Exit fullscreen mode
  • 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: [],
}
Enter fullscreen mode Exit fullscreen mode
  • 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',
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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');
    });
};
Enter fullscreen mode Exit fullscreen mode

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;
    });
};
Enter fullscreen mode Exit fullscreen mode

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;
    });
};
Enter fullscreen mode Exit fullscreen mode

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

Discussion (0)