Skip to content

Repository files navigation

Laravel Job Board with Advanced Filtering

Project Overview

This Laravel application is a Job Board with advanced filtering capabilities—similar to Airtable. It manages job listings using a combination of traditional relational database models and an Entity-Attribute-Value (EAV) design pattern. The application supports multiple job types, dynamic attributes, and complex filtering criteria.

Features

  • Core Job Model:

    • Fields: id, title, description, company_name, salary_min, salary_max, is_remote, job_type (enum: 'full-time', 'part-time', 'contract', 'freelance'), status (enum: 'draft', 'published', 'archived'), published_at, plus standard timestamps.
  • Many-to-Many Relationships:

    • Languages: Model with id and name (required programming languages).
    • Locations: Model with id, city, state, and country (job locations).
    • Categories: Model with id and name (job categories/departments).
  • EAV Implementation:

    • Attributes Table: Contains id, name, type, and options (JSON for select type).
    • Job Attribute Values: Table storing dynamic attribute values (id, job_id, attribute_id, value).
  • Advanced Filtering API:

    • RESTful API endpoint GET /api/jobs that supports filtering by:
      • Basic field types (text, numeric, boolean, enum, date)
      • Relationships (languages, locations, categories)
      • EAV attributes (text, number, boolean, select)
      • Logical operators (AND/OR, grouping)
    • Example query:
      /api/jobs?filter=(job_type=full-time AND (languages HAS_ANY (PHP,JavaScript))) AND (locations IS_ANY (Dubai,Remote)) AND attribute:years_experience>=3
      
  • Filter Builder Service:

    • A dedicated JobFilterService parses the filter parameters and builds an efficient Eloquent query that handles complex, grouped filtering (including EAV attributes).

Installation & Setup

Prerequisites

  • PHP >= 8.0
  • Composer
  • MySQL or PostgreSQL database (or any supported by Laravel)
  • Node.js & npm (for frontend assets, if applicable)

Installation Steps

  1. Clone the Repository:

    git clone https://github.com/HadiDevLabx/job-board.git
    cd job-board
  2. Install Dependencies:

    composer install
  3. Environment Setup:

    Copy the example environment file and update your settings:

    cp .env.example .env
    php artisan key:generate

    Update the .env file with your database credentials and other configurations.

  4. Database Migrations & Seeding:

    Run the migrations:

    php artisan migrate

    Seed the database (this includes sample data for jobs, languages, locations, categories, and EAV attributes):

    php artisan db:seed
  5. Serve the Application:

    php artisan serve

    Your application will be accessible at http://localhost:8000.

API Documentation

Endpoint: GET /api/jobs

This endpoint provides advanced filtering of job listings. The API accepts a query parameter filter that allows for a wide variety of operations.

Supported Filtering Capabilities

  1. Basic Filtering by Field:

    • Text/String fields (e.g., title, description, company_name):
      • Equality: =, !=
      • Contains: LIKE
    • Numeric fields (e.g., salary_min, salary_max):
      • Equality: =, !=
      • Comparison: >, <, >=, <=
    • Boolean fields (e.g., is_remote):
      • Equality: =, !=
    • Enum fields (e.g., job_type, status):
      • Equality: =, !=
      • Multiple values: IN
    • Date fields (e.g., published_at, created_at):
      • Equality: =, !=
      • Comparison: >, <, >=, <=
  2. Relationship Filtering:

    • Languages:
      • Example: (languages HAS_ANY (PHP,JavaScript))
    • Locations:
      • Example: (locations IS_ANY (Dubai,Remote))
    • Categories:
      • Similar syntax applies.
  3. EAV Filtering by Attribute:

    • Text attributes:
      • Equality: =, !=, Contains: LIKE
    • Number attributes:
      • Equality and Comparison: =, !=, >, <, >=, <=
    • Boolean attributes:
      • Equality: =, !=
    • Select attributes:
      • Equality: =, !=, Multiple values: IN
  4. Logical Operators:

    • Use AND and OR to combine conditions.
    • Use parentheses () to group conditions.
  5. Query Parameter Format:

    A sample complex filter:

    filter=(job_type=full-time AND (languages HAS_ANY (PHP,JavaScript))) AND (locations IS_ANY (dubai,Remote)) AND attribute:years_experience>=3
    

    In this format:

    • job_type=full-time filters jobs where the type is full-time.
    • languages HAS_ANY (PHP,JavaScript) finds jobs requiring at least one of the specified languages.
    • locations IS_ANY (Dubai,Remote) filters jobs by location.
    • attribute:years_experience>=3 applies an EAV filter on a dynamic attribute named years_experience.

Filter Builder

The JobFilterService is responsible for parsing the filter query parameter and constructing the appropriate Eloquent query. It uses query scopes, where clauses, and joins (including EAV attribute joins) to build efficient queries and handle grouped logical conditions.

Postman Collection

Below is a sample Postman collection JSON. Import this into Postman to test the API endpoints:

{
	"info": {
		"_postman_id": "351634db-ae3b-45ac-97d9-3d5eee0e63f6",
		"name": "Job Board Advanced Filtering API",
		"description": "Collection for testing advanced filtering capabilities on the /api/jobs endpoint.",
		"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
		"_exporter_id": "26611835"
	},
	"item": [
		{
			"name": "1. Text Field - Title Equality",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter?title=Senior Software Engineer 10",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter?title",
							"value": "Senior Software Engineer 10"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "2. Text Field - Description Contains (LIKE)",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=description LIKE Innovative",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "description LIKE Innovative"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "3. Numeric Field - Salary Min >= 60000",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=salary_min>=60000",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "salary_min>=60000"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "4. Numeric Field - Salary Max < 120000",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=salary_max<120000",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "salary_max<120000"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "5. Boolean Field - is_remote true",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=is_remote=true",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "is_remote=true"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "6. Enum Field - Job Type Equality",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=job_type=full-time",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "job_type=full-time"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "7. Enum Field - Job Type Multiple (IN)",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=job_type IN (full-time,contract)",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "job_type IN (full-time,contract)"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "8. Enum Field - Status Inequality",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=status!=draft",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "status!=draft"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "9. Date Field - Published At >= 2023-01-01",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=published_at>=2023-01-01",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "published_at>=2023-01-01"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "10. Date Field - Created At < 2023-12-31",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=created_at<2023-12-31",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "created_at<2023-12-31"
						}
					]
				}
			},
			"response": []
		},
		{
			"name": "11. Combined Complex Filter Example",
			"request": {
				"method": "GET",
				"header": [],
				"url": {
					"raw": "http://127.0.0.1:8000/api/jobs?filter=(job_type=full-time AND (languages HAS_ANY (PHP,JavaScript))) AND (locations IS_ANY (dubai,Remote)) AND attribute:years_experience>=3",
					"protocol": "http",
					"host": [
						"127",
						"0",
						"0",
						"1"
					],
					"port": "8000",
					"path": [
						"api",
						"jobs"
					],
					"query": [
						{
							"key": "filter",
							"value": "(job_type=full-time AND (languages HAS_ANY (PHP,JavaScript))) AND (locations IS_ANY (dubai,Remote)) AND attribute:years_experience>=3"
						}
					]
				}
			},
			"response": []
		}
	]
}

How to Import the Collection

  1. Open Postman.
  2. Click on Import and select the Raw Text option.
  3. Paste the JSON content above and click Import.
  4. You will now see the "Laravel Job Board API" collection with the sample request.

Assumptions & Design Decisions

  • Schema Design:
    The EAV implementation allows dynamic job attributes while keeping the core job table lean.

  • Filtering Query Syntax:
    The chosen query syntax is expressive yet simple. Grouping with parentheses and supporting logical operators enables complex filters.

  • Filter Builder:
    The JobFilterService is designed to be extensible for future attribute types and more advanced query operations.

  • Testing:
    Migrations, seeders, and a Postman collection are provided to help set up and test the API efficiently.

Running the Project

After installation and seeding the database, you can run your Laravel server using:

php artisan serve

Test the API endpoint using Postman or your browser at:

http://localhost:8000/api/jobs

This README file should guide you through installation, API usage, and testing. Be sure to adjust paths and URLs as needed for your deployment environment. Happy coding!

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages