Fresh Data Is Here for Person, Company & Employee Search API Endpoints Learn more

I Built a Diversity App to Pull Employee Composition of Any Company
A full-fledged app to pull employee data, with the full source code.

/ employee composition

I Built a Diversity App to Pull Employee Composition of Any Company

At Proxycurl, we don’t just believe in the power of diverse talent; we proudly embrace it. For that cause, we wanted something visual, something that would celebrate diversity in the workplace while serving as a medium for meaningful company insights.

As a developer, I spearheaded that challenge and coded a full interactive widget that takes a company's LinkedIn profile URL and instantly generates a vibrant pie-chart. This chart visually breaks down the company’s employee composition by country and job title, offering a clear picture of global representation. It’s a simple yet powerful tool to highlight what makes teams unique.

Here's the final product:

Note: Find the full code on our GitHub repo.

Today, in the spirit of celebrating diversity paired with functionality, I’ll guide you through how I developed this app, and how you can do the same using my source code.

The app will display your company's employee composition based on a company's LinkedIn profile URL. Specifically,

  1. Composition by country (such as 85% US, 15% UK)
  2. Composition by job titles (such as software engineer, designer, and account executive).

At Proxycurl, our dedication to fostering diversity inspired this project. But if you’re wondering how this app could serve you, the possibilities are huge:

Use case 1: for investment teams to identify investment signals

  • Let's say you're an early-stage VC firm, you can track startups with <10 employees and monitor for recent fast growth, which suggests that they're doing well and a potential for your investment.

Use case 2: for sales teams to identify buying signals

  • Let's say you're selling a service to optimize cloud cost in AWS, you can monitor for companies that are hiring lots of backend engineers and infer that they have heavy use of cloud server, potentially needing your service.

Use case 3: for marketing teams to track competitors' growth strategies

  • Let's say you're devising the marketing focus for the next quarter, and you observed that your competitor has been hiring aggressively for video content marketers, that would suggest that they're going after the video channel strongly.

Still not convinced? Here's an app demo to give you a push.

Prerequisites

Here's what you'll need to get started:

  • A basic aptitude for coding
  • Node v18+
  • Proxycurl API key - this is where we will pull the employee data based on their company LinkedIn profile URL
  • Basic knowledge of React.js & Next.js
  • Basic understanding of Tailwind CSS

Step 1: Create a new Next.js project

To start, you’ll need to create the foundation and structure of your app. Set up a new Next.js project.

npx create-next-app@latest employee-composition
cd employee-composition && npm run dev

Next, we’ll install essential UI components.

npx shadcn@latest add button card input

Now that we've set up the core framework and basic UI components, time to build from the bottom up!

Step 2: Pulling employee data via API

We will use Proxycurl to pull the entire employee list of a company input by the user. First, sign up for Proxycurl and get free credits. Under your dashboard, navigate to Company Endpoints, then find Employee Listing Endpoint. Here’s an example request:

import requests

api_key = 'YOUR_API_KEY'
headers = {'Authorization': 'Bearer ' + api_key}
api_endpoint = 'https://nubela.co/proxycurl/api/linkedin/company/employees/'
params = {
    'url': 'https://www.linkedin.com/company/microsoft',
    'coy_name_match': 'include',
    'use_cache': 'if-present',
    'country': 'us',
    'enrich_profiles': 'enrich',
    'role_search': '(co)?-?founder',
    'page_size': '10',
    'employment_status': 'current',
    'sort_by': 'recently-joined',
    'resolve_numeric_id': 'false',
}
response = requests.get(api_endpoint,
                        params=params,
                        headers=headers)

You can also choose to automatically enrich employee profiles with other data points using the enrich_profiles = enrich parameter.

Step 3: Create a Next.js API

Directly exposing your API key in the client is a security risk. So let's take a detour and create a Next.js API route that serves as a proxy for API calls. Add the following code to app/api/employeeListing/route.js:

import { NextResponse } from "next/server";

export async function GET(req) {
  const { searchParams } = new URL(req.url);
  const company_url = searchParams.get("company_url");
  const next_page_url = searchParams.get("next_page_url")
  const after = searchParams.get("after");

  const finalUrl =
    next_page_url && next_page_url !== 'undefined'
      ? `${next_page_url}&after=${after}&enrich_profiles=enrich`
      : `https://nubela.co/proxycurl/api/linkedin/company/employees/?url=${company_url}&enrich_profiles=enrich`;


  try {
    const response = await fetch(finalUrl, {
      headers: {
        Authorization: `Bearer ${process.env.PROXYCURL_API_KEY}`,
      },
    });

    const data = await response.json();
    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json({ error });
  }
}

Step 4: Input company LinkedIn URL

You need to input a LinkedIn company URL to fetch employee data. For this, we’ll use the Input and Button components we installed earlier.

In app/page.js , add the input & button components from shadcn.

"use client";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";

const [companyUrl, setCompanyUrl] = useState("");

const handleKeyPress = (e) => {
    if (e.key === "Enter") {
      handleSubmit();
    }
};

 return (
<div className="mx-auto w-full max-w-2xl min-h-[100vh] flex flex-col p-4">
    <div className="flex flex-col gap-4 mx-auto w-full sm:flex-row sm:w-auto">
      <Input
        className="w-full sm:w-96"
        placeholder="LinkedIn Company URL"
        value={companyUrl}
        onChange={(e) => setCompanyUrl(e.target.value)}
        onKeyPress={handleKeyPress}
      />
      <Button className="w-full sm:w-auto" onClick={handleSubmit}>
        Submit
      </Button>
    </div>
</div>

This creates an input field and a button for submitting the LinkedIn URL. It should look something like this:

Great! We are halfway there!

Step 5: Fetching employee data and handling pagination

Now let's define the handleSubmit in app/page.js. The handleSubmit function triggers the API call. Here’s how it’s implemented in app/page.js:

const handleSubmit = async (e) => {
    e.preventDefault();
    await fetchEmployeeListing();
};

API call done! But what about pagination? The logic here is if there's next_page URL in the response, we will call that URL until there isn't one, which is indicated by next_page being null.

The fetchEmployeeListing function handles API requests, including pagination. If next_page exists in the response, it fetches data until all pages are retrieved.

  const fetchEmployeeListing = async (next_page_url) => {
    try {
      setIsLoading(true);
      const res = await fetch(
        `/api/employeeListing?company_url=${companyUrl}&next_page_url=${next_page_url}`,
        {
          method: "GET",
          headers: {
            "Content-Type": "application/json",
          },
        },
      );
      const data = await res.json();

      const cleanData = data.employees.filter(
        (employee) => employee.profile !== null,
      );

      const company_id = companyUrl.split("/company/")[1].replace("/", "");

      handleSaveQuery({
        company_id: company_id,
        employees: cleanData,
      });

      if (data.next_page !== null) {
        fetchEmployeeListing(data.next_page);
      } else {
        const employeeListing = JSON.parse(
          localStorage.getItem("employeeListing"),
        );

        const countryCount = {};

        employeeListing[company_id].employees.forEach((employee) => {
          const country = employee.profile.country_full_name;
          countryCount[country] = (countryCount[country] || 0) + 1;
        });

        const occupationCount = {};

        employeeListing[company_id].employees.forEach((employee) => {
          const occupation = employee.profile.occupation;
          occupationCount[occupation] = (occupationCount[occupation] || 0) + 1;
        });

        try {
          const res = await fetch(`/api/categorizeJobTitle`, {
            method: "POST",
            body: JSON.stringify({ job_title: occupationCount }),
          });

          const categorizedOccupationJSON = await res.json();
          const categorizedOccupationCount = JSON.parse(categorizedOccupationJSON);

          setCountryData({
            labels: Object.keys(countryCount),
            data: Object.values(countryCount),
          });

          setOccupationData({
            labels: Object.keys(categorizedOccupationCount),
            data: Object.values(categorizedOccupationCount),
          });
        } catch (error) {
          console.log(error);
        }

        setIsLoading(false);
        return;
      }
    } catch (error) {
      console.log(error);
    }
  };

Now, it's time to implement the functionality to store the results of the API calls in your local storage:

 const handleSaveQuery = ({ company_id, employees }) => {
    const storedEmployeeListing =
      JSON.parse(localStorage.getItem("employeeListing")) || [];

    if (storedEmployeeListing[company_id]?.employees?.length > 0) {
      const updatedEmployees = [
        ...storedEmployeeListing[company_id].employees,
        ...employees,
      ];
      localStorage.setItem(
        "employeeListing",
        JSON.stringify({
          ...storedEmployeeListing,
          [company_id]: {
            employees: updatedEmployees,
          },
        }),
      );
    } else {
      localStorage.setItem(
        "employeeListing",
        JSON.stringify({
          ...storedEmployeeListing,
          [company_id]: {
            employees: employees,
          },
        }),
      );
    }
  };

Step 6: Displaying the data in a pie chart

Now the fun part. After getting all of data, we want to be able to easily understand them, the various countries and job titles of the employees in their proper percentages.

For this, we'll use a pie chart component from ApexCharts.

First, create app/components/Pie.jsx:

'use client';
import React from 'react';
import dynamic from 'next/dynamic';

const Chart = dynamic(() => import('react-apexcharts'), { ssr: false });

function PieChart({chartData}) {

  const { labels, data } = chartData;

  if (!data || !labels ) return null;

  const options = {
    chart: {
      type: 'pie',
      foreColor: 'hsl(var(--foreground))',
      fontFamily: 'var(--font-geist-sans)'
    },
    labels: labels,
    colors: [
      'hsl(var(--chart-1))',
      'hsl(var(--chart-2))',
      'hsl(var(--chart-3))',
      'hsl(var(--chart-4))',
      'hsl(var(--chart-5))',
    ],
    legend: {
      position: 'bottom',
      fontSize: '12px',
      fontFamily: 'var(--font-geist-sans)',
      height: 'auto',
      offsetY: 10,
      labels: {
        colors: 'hsl(var(--foreground))'
      },
      markers: {
        width: 8,
        height: 8,
      },
      itemMargin: {
        horizontal: 8,
        vertical: 3
      },
      containerMargin: {
        top: 12
      },
      formatter: function(seriesName, opts) {
        return seriesName.length > 30 ? seriesName.substring(0, 30) + '...' : seriesName;
      }
    },
    tooltip: {
      style: {
        fontSize: '14px',
        fontFamily: 'var(--font-geist-sans)'
      }
    },
    responsive: [{
      breakpoint: 480,
      options: {
        chart: {
          width: 300
        },
        legend: {
          position: 'bottom'
        }
      }
    }]
  };

  return (
    <div className="w-[500px]">
      <Chart
        options={options}
        series={data}
        type="pie"
        width="500"
        height="500"
      />
    </div>
  );
}

export default PieChart;

What you'll get is this beautiful pie chart displaying all the data you have, in a slick and visually-aesthetic manner.

Congratulations! You now have a working employee composition App.

Wrapping Up

This project was a fun little way to express our values and I sure had fun on this project. But remember, nothing is set in stone and don't fret to add your own twist along the way.

Beyond Proxycurl's commitment to diversity, this project also serves an excellent example to what our API can do in terms of real-world use cases. Proxycurl’s API capabilities surpass just enriching employee and company data; it serves as powerful tools for applications requiring detailed professional insights.

Take the first step. Sign up for Proxycurl today!

What does diversity cover?

A diverse workforce represents the a vast range of people. People with different nationalities, ethnicities, religions, people with disabilities, and both men and women. It also means valuing and cherishing those differences.

How do you align yourself with workplace diversity?

The best way you can align yourself with diversity is to highlight your policies and initiatives to support the cause. Share stories and represent your commitment by integrating visual tools that highlight your team’s composition. It’s a simple yet impactful way to celebrate diversity and share your values with your audience.

Why do I need an employee composition app?

A commitment to celebrate and embrace diversity is a big reason enough. But if you are looking for more practical use cases:

  • For investors: Track companies with rapid growth as investment signals.
  • For sales teams: Identify potential leads based on hiring trends.
  • For marketers: Monitor competitor strategies based on hiring patterns.

Can the app handle large companies with thousands of employees?

Yes, the app supports pagination and retrieves data in batches to manage large datasets efficiently.

Subscribe to our newsletter

Get the latest news from Proxycurl

Featured Articles

Here’s what we’ve been up to recently.