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.
-
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.
- Fields:
-
Many-to-Many Relationships:
- Languages: Model with
idandname(required programming languages). - Locations: Model with
id,city,state, andcountry(job locations). - Categories: Model with
idandname(job categories/departments).
- Languages: Model with
-
EAV Implementation:
- Attributes Table: Contains
id,name,type, andoptions(JSON for select type). - Job Attribute Values: Table storing dynamic attribute values (
id,job_id,attribute_id,value).
- Attributes Table: Contains
-
Advanced Filtering API:
- RESTful API endpoint
GET /api/jobsthat 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
- RESTful API endpoint
-
Filter Builder Service:
- A dedicated
JobFilterServiceparses the filter parameters and builds an efficient Eloquent query that handles complex, grouped filtering (including EAV attributes).
- A dedicated
- PHP >= 8.0
- Composer
- MySQL or PostgreSQL database (or any supported by Laravel)
- Node.js & npm (for frontend assets, if applicable)
-
Clone the Repository:
git clone https://github.com/HadiDevLabx/job-board.git cd job-board -
Install Dependencies:
composer install
-
Environment Setup:
Copy the example environment file and update your settings:
cp .env.example .env php artisan key:generate
Update the
.envfile with your database credentials and other configurations. -
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
-
Serve the Application:
php artisan serve
Your application will be accessible at
http://localhost:8000.
This endpoint provides advanced filtering of job listings. The API accepts a query parameter filter that allows for a wide variety of operations.
-
Basic Filtering by Field:
- Text/String fields (e.g., title, description, company_name):
- Equality:
=,!= - Contains:
LIKE
- Equality:
- Numeric fields (e.g., salary_min, salary_max):
- Equality:
=,!= - Comparison:
>,<,>=,<=
- Equality:
- Boolean fields (e.g., is_remote):
- Equality:
=,!=
- Equality:
- Enum fields (e.g., job_type, status):
- Equality:
=,!= - Multiple values:
IN
- Equality:
- Date fields (e.g., published_at, created_at):
- Equality:
=,!= - Comparison:
>,<,>=,<=
- Equality:
- Text/String fields (e.g., title, description, company_name):
-
Relationship Filtering:
- Languages:
- Example:
(languages HAS_ANY (PHP,JavaScript))
- Example:
- Locations:
- Example:
(locations IS_ANY (Dubai,Remote))
- Example:
- Categories:
- Similar syntax applies.
- Languages:
-
EAV Filtering by Attribute:
- Text attributes:
- Equality:
=,!=, Contains:LIKE
- Equality:
- Number attributes:
- Equality and Comparison:
=,!=,>,<,>=,<=
- Equality and Comparison:
- Boolean attributes:
- Equality:
=,!=
- Equality:
- Select attributes:
- Equality:
=,!=, Multiple values:IN
- Equality:
- Text attributes:
-
Logical Operators:
- Use
ANDandORto combine conditions. - Use parentheses
()to group conditions.
- Use
-
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>=3In this format:
job_type=full-timefilters 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>=3applies an EAV filter on a dynamic attribute namedyears_experience.
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.
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": []
}
]
}- Open Postman.
- Click on Import and select the Raw Text option.
- Paste the JSON content above and click Import.
- You will now see the "Laravel Job Board API" collection with the sample request.
-
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:
TheJobFilterServiceis 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.
After installation and seeding the database, you can run your Laravel server using:
php artisan serveTest 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!