A robust Spring Boot application that exposes Employee and Department data via a powerful GraphQL API. This service supports complex relationships, including Employee-Department, Employee-Salary, and Employee-Title associations. All API access is securely protected using an API Key passed in the request headers.
GraphQL offers several advantages over traditional REST APIs, making it a compelling choice for API development:
- Precise Data Retrieval β Clients can request exactly the data they need, reducing over-fetching and under-fetching issues common in REST. This leads to more efficient data usage and optimized network performance.
- Single Endpoint β Unlike REST, which often requires multiple endpoints for different resources, GraphQL operates through a single endpoint. This simplifies API interactions and reduces the complexity of managing numerous endpoints.
- Flexible and Strongly Typed Schema β GraphQL uses a type system to describe data, allowing clients to understand available data and operations clearly. This enhances development efficiency and reduces errors.
- Efficient Data Aggregation β Clients can fetch related data in a single request, minimizing the need for multiple roundtrips to the server. This is particularly beneficial for complex systems where data resides in different sources.
- API Evolution Without Versioning β GraphQL enables APIs to evolve by adding new fields and types without impacting existing queries. Deprecated fields can be managed gracefully, reducing the need for versioning.
These features make GraphQL a powerful alternative to REST, offering more efficient, flexible, and developer-friendly API interactions.
The technology used in this project are:
Spring Boot Starter Web
β Provides the foundational web support for the application, enabling HTTP handling and server-side capabilities for GraphQL over HTTP.Spring Boot Starter GraphQL
β Integrates GraphQL Java into the Spring ecosystem.GraphQL Java Extended Scalars
β a library that provides additional scalar types for GraphQL Java, such asDateTime
,URL
, andBigDecimal
.Spring Boot Starter Validation
β Adds support for Java Bean Validation, ensuring the integrity of incoming request data through declarative annotations like@NotNull
,@Size
.Spring Boot Starter Security
β Provides the necessary components for securing the application.PostgreSQL
β Database for persisting employee, department, salary, and title data.Hibernate
β Simplifying database interactions.Lombok
β Reducing boilerplate code.
The project is organized into the following package structure:
graphql-employee-management/
βββ src/main/java/com/yoanesber/graphql_employee_management/
β βββ πconfig/ # Configuration classes for GraphQL, Security, CORS, etc.
β βββ πcontroller/ # GraphQL resolvers for handling queries and mutations
β βββ πdto/ # Data Transfer Objects for request/response shaping
β βββ πentity/ # JPA entities representing tables in the database
β βββ πhandler/ # Custom API key authentication filter and related logic
β βββ πrepository/ # Spring Data JPA repositories for data access
β βββ πservice/ # Service interfaces defining business logic contracts
β β βββ πimpl/ # Implementations of the service interfaces
βββ src/main/resources/
β βββ πgraphql/ # GraphQL schema files (e.g., schema.graphqls)
β βββ application.properties # Application configuration file (API key, DB, etc.)
The application uses externalized configuration via Spring Boot's application.properties
file, leveraging environment variables to support flexible deployment across different environments (development, staging, production, etc.).
Below is a breakdown of the key configurations:
# Application environment variables
spring.application.name=graphql-employee-management
server.port=${APP_PORT}
spring.profiles.active=${SPRING_PROFILES_ACTIVE}
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:${SPRING_DATASOURCE_PORT}/${SPRING_DATASOURCE_DB}?currentSchema=${SPRING_DATASOURCE_SCHEMA}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
# API configuration
app.api.key=${APP_API_KEY}
You can configure these variables via a .env
file, CI/CD pipeline, or your host systemβs environment.
The following is the database schema for the PostgreSQL database used in this project:
CREATE SCHEMA your_schema;
-- table your_schema.employee
CREATE TABLE IF NOT EXISTS your_schema.employee (
id bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY ( INCREMENT 1 START 1 MINVALUE 1 MAXVALUE 9223372036854775807 CACHE 1 ),
birth_date date NOT NULL,
first_name character varying(20) NOT NULL,
last_name character varying(20),
gender character varying(1) NOT NULL,
hire_date date NOT NULL,
active boolean DEFAULT false NOT NULL,
created_by bigint NOT NULL,
created_date timestamp with time zone DEFAULT now() NOT NULL,
updated_by bigint NOT NULL,
updated_date timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT employee_pkey PRIMARY KEY (id),
);
-- table your_schema.department
CREATE TABLE IF NOT EXISTS your_schema.department (
id character varying(4) NOT NULL,
dept_name character varying(40) NOT NULL,
active boolean NOT NULL,
created_by bigint NOT NULL,
created_date timestamp with time zone DEFAULT now() NOT NULL,
updated_by bigint NOT NULL,
updated_date timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT department_pkey PRIMARY KEY (id)
);
-- table your_schema.department_employee
CREATE TABLE IF NOT EXISTS your_schema.department_employee (
employee_id bigint NOT NULL,
department_id character varying(255) NOT NULL,
from_date date NOT NULL,
to_date date NOT NULL,
CONSTRAINT department_employee_pkey PRIMARY KEY (employee_id, department_id),
CONSTRAINT department_employee_fkey1 FOREIGN KEY (employee_id) REFERENCES your_schema.employee(id) ON UPDATE RESTRICT ON DELETE CASCADE,
CONSTRAINT department_employee_fkey2 FOREIGN KEY (department_id) REFERENCES your_schema.department(id) ON UPDATE RESTRICT ON DELETE CASCADE
);
-- table your_schema.salary
CREATE TABLE IF NOT EXISTS your_schema.salary (
employee_id bigint NOT NULL,
amount bigint NOT NULL,
from_date date NOT NULL,
to_date date NOT NULL,
CONSTRAINT salary_pkey PRIMARY KEY (employee_id, from_date),
CONSTRAINT salary_fkey FOREIGN KEY (employee_id) REFERENCES your_schema.employee(id) ON UPDATE RESTRICT ON DELETE CASCADE
);
-- table your_schema.title
CREATE TABLE IF NOT EXISTS your_schema.title (
employee_id bigint NOT NULL,
title character varying(50) NOT NULL,
from_date date NOT NULL,
to_date date,
CONSTRAINT title_pkey PRIMARY KEY (employee_id, title, from_date),
CONSTRAINT title_fkey FOREIGN KEY (employee_id) REFERENCES your_schema.employee(id) ON UPDATE RESTRICT ON DELETE CASCADE
);
The following is the relationship between tables:
- Department β DepartmentEmployee (
One-to-Many
) - Employee β DepartmentEmployee (
One-to-Many
) - Employee β SalaryEmployee (
One-to-Many
) - Employee β TitleEmployee (
One-to-Many
)
These tables are managed using EmbeddedId
to define composite primary keys:
- department_employee (employee_id, department_id)
- salary (employee_id, from_date)
- title (employee_id, title, from_date)
To get started with the Spring Boot GraphQL API for Employee & Department Management, follow the steps below to configure your environment properly.
- Ensure you have Git installed on your Windows machine, then clone the repository to your local environment:
git clone https://github.com/yoanesber/Spring-Boot-Graphql-Employee-Management.git
cd Spring-Boot-Graphql-Employee-Management
- Set up PostgreSQL
The application connects to a PostgreSQL database for persistent storage of employee, department, salary, and title data.
- Run the provided DDL script to set up the database schema
- Ensure the following environment variables are defined before starting the application:
# Database configuration
spring.datasource.url=jdbc:postgresql://localhost:${SPRING_DATASOURCE_PORT}/${SPRING_DATASOURCE_DB}?currentSchema=${SPRING_DATASOURCE_SCHEMA}
spring.datasource.username=${SPRING_DATASOURCE_USERNAME}
spring.datasource.password=${SPRING_DATASOURCE_PASSWORD}
Once configured, make sure the PostgreSQL instance is running and accessible before starting the Spring Boot application.
- API Key Configuration
To secure access to the GraphQL API, the application requires an API key to be included in each request via the X-API-KEY
header.
# API configuration
app.api.key=${APP_API_KEY}
Make sure to set the APP_API_KEY
environment variable in your local environment, .env
file, or deployment platform. Requests without a valid API key will receive an authorization error.
- Run the application locally
Make sure PostgreSQL is running, then execute:
mvn spring-boot:run
- The server will start at:
http://localhost:8080/graphql
Once the application is running (at http://localhost:8080/graphql), you can test the GraphQL API using tools like:
All GraphQL requests to this API must include a valid X-API-KEY
header for authentication. This API key serves as a security mechanism to restrict unauthorized access to the protected resources. When testing queries or mutations using tools like Postman or GraphQL Playground, make sure to add the following header to every request:
Headers:
Content-Type: application/json
X-API-KEY: your-api-key-here
Note: If the API key is missing or invalid, the server will respond with an authentication error and deny access to the requested operation.
Invalid or Missing API Key Response:
{
"error": "Unauthorized: Invalid or missing API key"
}
Apis to create, retrieve, update, delete Department.
- Save a New Department
Request:
mutation SaveDepartment {
saveDepartment(
departmentCreateDTO: { id: "d001", deptName: "Support", active: false, createdBy: "1" }
) {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Successful Response:
{
"data": {
"saveDepartment": {
"id": "d001",
"deptName": "Support",
"active": false,
"createdBy": 1,
"createdDate": "2025-04-10T13:25:39.874+07:00",
"updatedBy": 1,
"updatedDate": "2025-04-10T13:25:39.874+07:00"
}
}
}
β Example: Duplicate Department ID
Request:
mutation SaveDepartment {
saveDepartment(
departmentCreateDTO: { id: "d001", deptName: "Support", active: false, createdBy: "1" }
) {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Response (Duplicate ID Error):
{
"errors": [
{
"message": "Error saving department: Department with id d001 already exists",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"saveDepartment"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"saveDepartment": null
}
}
- Get All Departments
β Full Fields Retrieval
You can request all available fields for each department.
Request:
query GetAllDepartments {
getAllDepartments {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Successful Response:
{
"data": {
"getAllDepartments": [
{
"id": "d001",
"deptName": "Marketing",
"active": true,
"createdBy": 1,
"createdDate": "2024-10-07T17:51:24.616Z",
"updatedBy": 1,
"updatedDate": "2024-11-11T16:58:30.929Z"
},
{
"id": "d002",
"deptName": "Finance",
"active": true,
"createdBy": 1,
"createdDate": "2024-10-07T17:51:24.616Z",
"updatedBy": 1,
"updatedDate": "2024-10-07T17:51:24.616Z"
},
...
]
}
}
β Partial Fields Retrieval
GraphQL allows you to fetch only the fields you need, reducing unnecessary data transfer and improving performance.
Request (only id and deptName):
query GetAllDepartments {
getAllDepartments {
id
deptName
}
}
Response:
{
"data": {
"getAllDepartments": [
{
"id": "d001",
"deptName": "Marketing"
},
{
"id": "d002",
"deptName": "Finance"
},
...
]
}
}
- Get Department by ID
Request:
query GetDepartmentById {
getDepartmentById(id: "d001") {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Successful Response:
{
"data": {
"getDepartmentById": {
"id": "d001",
"deptName": "Marketing",
"active": true,
"createdBy": 1,
"createdDate": "2024-10-07T17:51:24.616Z",
"updatedBy": 1,
"updatedDate": "2024-11-11T16:58:30.929Z"
}
}
}
β Example: Get Department by Invalid ID
Request:
query GetDepartmentById {
getDepartmentById(id: "xxx") {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Response (If Department Not Found):
{
"errors": [
{
"message": "Error getting department by id: Department with id xxx does not exist",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"getDepartmentById"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"getDepartmentById": null
}
}
β Example: Missing Required Field
Request:
query GetDepartmentById {
getDepartmentById(id: "") {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Response (Validation Error):
{
"errors": [
{
"message": "Department ID cannot be blank",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"getDepartmentById"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"getDepartmentById": null
}
}
β Example: Invalid Syntax
Request:
query GetDepartmentById {
getDepartmentById() {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Response (Invalid Syntax):
{
"errors": [
{
"message": "Invalid syntax with offending token ')' at line 2 column 23",
"locations": [
{
"line": 2,
"column": 23
}
],
"extensions": {
"classification": "InvalidSyntax"
}
}
]
}
- Update an Existing Department
Request:
mutation UpdateDepartment {
updateDepartment(
id: "d001"
departmentUpdateDTO: { deptName: "Supply & Chain", active: true, updatedBy: "2" }
) {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Successful Response:
{
"data": {
"updateDepartment": {
"id": "d001",
"deptName": "Supply & Chain",
"active": true,
"createdBy": 1,
"createdDate": "2025-04-10T06:25:39.874Z",
"updatedBy": 2,
"updatedDate": "2025-04-10T14:07:11.223+07:00"
}
}
}
β Example: Update Department by Invalid ID
Request:
mutation UpdateDepartment {
updateDepartment(
id: "xxx"
departmentUpdateDTO: { deptName: "Supply & Chain", active: true, updatedBy: "2" }
) {
id
deptName
active
createdBy
createdDate
updatedBy
updatedDate
}
}
Response (If Department Not Found):
{
"errors": [
{
"message": "Department with id xxx does not exist",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"updateDepartment"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"updateDepartment": null
}
}
- Delete a Department
Request:
mutation DeleteDepartment {
deleteDepartment(id: "d001")
}
Successful Response:
{
"data": {
"deleteDepartment": true
}
}
The following GraphQL operations allow you to manage employee records along with their department, salary, and title associations.
- Save a New Employee
Request:
mutation SaveEmployee {
saveEmployee(
employeeCreateDTO: {
birthDate: "1990-07-09"
firstName: "Jenny"
lastName: null
gender: "F"
hireDate: "2025-01-01"
activeStatus: true
createdBy: "1"
departments: [{
departmentId: "d001"
fromDate: "2025-01-01"
toDate: "2025-03-30"
}]
salaries: [{
amount: 60116
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
titles: [{
title: "Senior Engineer"
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
}
) {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
}
}
Successful Response:
{
"data": {
"saveEmployee": {
"id": 1,
"birthDate": "1990-07-09",
"firstName": "Jenny",
"lastName": null,
"gender": "F",
"hireDate": "2025-01-01",
"activeStatus": true,
"createdBy": 1,
"createdDate": "2025-04-10T14:37:03.055+07:00",
"updatedBy": 1,
"updatedDate": "2025-04-10T14:37:03.055+07:00"
}
}
}
β Example: Missing First Name
Request:
mutation SaveEmployee {
saveEmployee(
employeeCreateDTO: {
birthDate: "1990-07-09"
firstName: null
lastName: null
gender: "F"
hireDate: "2025-01-01"
activeStatus: true
createdBy: "1"
departments: [{
departmentId: "d001"
fromDate: "2025-01-01"
toDate: "2025-03-30"
}]
salaries: [{
amount: 60116
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
titles: [{
title: "Senior Engineer"
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
}
) {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
}
}
Response:
{
"errors": [
{
"message": "[saveEmployee.employeeCreateDTO.firstName] First Name cannot be blank",
"locations": [
{
"line": 3,
"column": 5
}
],
"path": [
"saveEmployee"
],
"extensions": {
"classification": "ValidationError"
}
}
],
"data": {
"saveEmployee": null
}
}
- Get All Employees
β Full Fields Retrieval
You can request all available fields for each employee.
Request:
query GetAllEmployees {
getAllEmployees {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
departments {
departmentId
fromDate
toDate
}
salaries {
amount
fromDate
toDate
}
titles {
title
fromDate
toDate
}
}
}
Successful Response:
{
"data": {
"getAllEmployees": [
{
"id": 10001,
"birthDate": "1990-07-09",
"firstName": "Jenny",
"lastName": null,
"gender": "F",
"hireDate": "2025-01-01",
"activeStatus": true,
"createdBy": 1,
"createdDate": "2025-04-10T07:37:03.055Z",
"updatedBy": 1,
"updatedDate": "2025-04-10T07:37:03.055Z",
"departments": [
{
"departmentId": "d001",
"fromDate": "2025-01-01",
"toDate": "2025-03-30"
}
],
"salaries": [
{
"amount": 60116,
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
],
"titles": [
{
"title": "Senior Engineer",
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
]
},
...
]
}
}
β Partial Fields Retrieval
GraphQL allows you to fetch only the fields you need, reducing unnecessary data transfer and improving performance.
Request (only id, firstName, departments):
query GetAllEmployees {
getAllEmployees {
id
firstName
departments {
departmentId
fromDate
toDate
}
}
}
Response:
{
"data": {
"getAllEmployees": [
{
"id": 10001,
"firstName": "Jenny",
"departments": [
{
"departmentId": "d002",
"fromDate": "2025-01-01",
"toDate": "2025-03-30"
}
]
},
{
"id": 10002,
"firstName": "Bezalel",
"departments": [
{
"departmentId": "d010",
"fromDate": "2024-01-01",
"toDate": "2024-09-30"
}
]
},
...
]
}
}
- Get Employee by ID
Request:
query GetEmployeeById {
getEmployeeById(id: "10001") {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
departments {
departmentId
fromDate
toDate
}
salaries {
amount
fromDate
toDate
}
titles {
title
fromDate
toDate
}
}
}
Successful Response:
{
"data": {
"getEmployeeById": {
"id": 10001,
"birthDate": "1990-07-09",
"firstName": "Jenny",
"lastName": null,
"gender": "F",
"hireDate": "2025-01-01",
"activeStatus": true,
"createdBy": 1,
"createdDate": "2025-04-10T07:37:03.055Z",
"updatedBy": 1,
"updatedDate": "2025-04-10T07:37:03.055Z",
"departments": [
{
"departmentId": "d001",
"fromDate": "2025-01-01",
"toDate": "2025-03-30"
}
],
"salaries": [
{
"amount": 60116,
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
],
"titles": [
{
"title": "Senior Engineer",
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
]
}
}
}
β Example: Invalid ID
Request:
query GetEmployeeById {
getEmployeeById(id: "101100") {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
departments {
departmentId
fromDate
toDate
}
salaries {
amount
fromDate
toDate
}
titles {
title
fromDate
toDate
}
}
}
Response (If Department Not Found):
{
"errors": [
{
"message": "Employee with id 101100 does not exist",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"getEmployeeById"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"getEmployeeById": null
}
}
- Update an Existing Employee
Request:
mutation UpdateEmployee {
updateEmployee(
id: "10001"
employeeUpdateDTO: {
birthDate: "1990-07-08"
activeStatus: false
firstName: "Jenny"
lastName: "Isabelle"
gender: "F"
hireDate: "2025-01-01"
updatedBy: 1
departments: [{
departmentId: "d002"
fromDate: "2025-01-01"
toDate: "2025-03-30"
}]
salaries: [{
amount: 60117
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
titles: [{
title: "Senior Engineer"
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
}
) {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
departments {
departmentId
fromDate
toDate
}
salaries {
amount
fromDate
toDate
}
titles {
title
fromDate
toDate
}
}
}
Successful Response:
{
"data": {
"updateEmployee": {
"id": 10001,
"birthDate": "1990-07-08",
"firstName": "Jenny",
"lastName": "Isabelle",
"gender": "F",
"hireDate": "2025-01-01",
"activeStatus": false,
"createdBy": 1,
"createdDate": "2025-04-10T07:37:03.055Z",
"updatedBy": 1,
"updatedDate": "2025-04-10T14:50:00.269+07:00",
"departments": [
{
"departmentId": "d002",
"fromDate": "2025-01-01",
"toDate": "2025-03-30"
}
],
"salaries": [
{
"amount": 60117,
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
],
"titles": [
{
"title": "Senior Engineer",
"fromDate": "2000-01-01",
"toDate": "2005-12-31"
}
]
}
}
}
β Example: Update Employee by Invalid ID
Request:
mutation UpdateEmployee {
updateEmployee(
id: "100011"
employeeUpdateDTO: {
birthDate: "1990-07-08"
activeStatus: false
firstName: "Jenny"
lastName: "Isabelle"
gender: "F"
hireDate: "2025-01-01"
updatedBy: 1
departments: [{
departmentId: "d002"
fromDate: "2025-01-01"
toDate: "2025-03-30"
}]
salaries: [{
amount: 60117
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
titles: [{
title: "Senior Engineer"
fromDate: "2000-01-01"
toDate: "2005-12-31"
}]
}
) {
id
birthDate
firstName
lastName
gender
hireDate
activeStatus
createdBy
createdDate
updatedBy
updatedDate
departments {
departmentId
fromDate
toDate
}
salaries {
amount
fromDate
toDate
}
titles {
title
fromDate
toDate
}
}
}
Response (If Employee Not Found):
{
"errors": [
{
"message": "Employee with id 100011 does not exist",
"locations": [
{
"line": 2,
"column": 5
}
],
"path": [
"updateEmployee"
],
"extensions": {
"classification": "DataFetchingException"
}
}
],
"data": {
"updateEmployee": null
}
}
- Delete an Employee
Request:
mutation DeleteEmployee {
deleteEmployee(id: "10030")
}
Successful Response:
{
"data": {
"deleteEmployee": true
}
}
- This project is a backend-focused API, intended to manage Employees and their relationships with Departments, Salaries, and Titles using GraphQL.
- All operations are protected by a lightweight API key mechanism using the
X-API-KEY
header. - The schema and DTOs are kept clean and follow GraphQL best practices for separation of input and output types.
- DTO input fields are validated using Java Bean Validation (JSR-380) annotations such as
@NotBlank
,@NotNull
, and custom constraints to ensure data integrity at the API level. - Validation errors are consistently handled through a custom
GraphQLExceptionConfig
, which transforms exceptions into structured and informative GraphQL error responses. - Clean architecture and clear separation of concerns are implemented using service layers, DTO mappings, and repository abstraction.
- OAuth2 / JWT Security Integration
Replace the static API key mechanism with OAuth2 or JWT-based authentication using Spring Security. This will enhance scalability, token expiration control, and multi-user access management.
- GraphQL Subscriptions
Implement GraphQL subscriptions to enable real-time notifications when an employee is created, updated, or deleted. This would be useful for reactive client-side applications.
- REST API + PostgreSQL Repository, check out REST API Spring Boot One To Many example with Hibernate and PostgreSQL.
- REST API + JWT Authentication Repository, check out Netflix Shows REST API with JWT Authentication.