Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 8

Designing Project Management Service: API & Data Flows

Recommended resources

In our last session, we designed the "front door" to our SaaS: the tenant management and authentication services. We established a secure, "account-first" registration flow and defined how users will authenticate using JWTs that contain their user ID, tenant ID, and permissions. This self-contained token is the key to our stateless, multi-tenant architecture.

Today, we move from the entryway into the main rooms of our application. Our objective is to create the low-level design for the project management service, defining the API contracts and data flows for projects, tasks, and milestones. This is where we apply the authentication and RBAC framework we've designed to the core features of our construction management system. We will be translating our high-level requirements into a precise, developer-ready API specification.

From Data Model to API Resources

Before defining endpoints, it's crucial to ground our API design in the underlying data model. A well-designed RESTful API acts as an intuitive and logical interface to the application's core entities. It shouldn't be an arbitrary collection of functions, but a clear representation of the resources the system manages.

Let's look at a typical Entity-Relationship Diagram (ERD) for a project management system.

This ERD illustrates the core entities like projects, tasks, and users, and their relationships. We will use this as a conceptual blueprint for our API resources.

As the diagram shows, a project is a central entity. It has a relationship with tasks, milestones, users, and clients. In RESTful design, we model these entities as resources, which are identified by URLs. The actions we perform on them (Create, Read, Update, Delete) are mapped to standard HTTP methods.

To learn the foundational principles of this process, the following video provides an excellent overview of how to design APIs like a senior engineer, focusing on the core concepts we'll be using today.

How to Design APIs Like a Senior Engineer (REST, GraphQL, Auth, Security)

This segment from Hayk Simonyan's channel explains how to model business domains into RESTful resources. It covers best practices for URL design, including the use of plural nouns and handling nested resources.

Please watch the section on resource modeling and URL design from the beginning. Pay close attention to how products and reviews are modeled as collections and how their relationship is expressed through a nested URL.

As you just saw, the key is to use plural nouns for collections (e.g., /projects) and to represent parent-child relationships through nested paths (e.g., /projects/{project_id}/tasks).


The Three Pillars of API Design Decisions

When designing these endpoints, our decisions should be driven by three factors we established during the requirements phase:

  1. Access Patterns: How will the frontend client query this data? An API to fetch all tasks for a single project (GET /projects/{id}/tasks) is a clear access pattern.
  2. Data Volume: How much data are we dealing with? For lists of projects or tasks, we must design for pagination from day one to handle potentially large datasets efficiently.
  3. Consistency Requirements: Our use of foreign keys in the database ensures referential integrity (a task must belong to a valid project). Our API design must honor and expose these relationships logically.

With these principles in mind, let's design the API contracts.


Low-Level Design: Project Management Service

We will organize our endpoints logically around the core resources: projects, tasks, and milestones. All endpoints are prefixed with /api/v1 and are protected by our authentication and RBAC dependencies. The tenant_id is always derived from the user's JWT, never accepted from a request body or URL parameter.

1. Projects Resource

A project is the top-level container for all related work.

Action Method & Endpoint Description
List Projects GET /projects Retrieves a paginated list of projects for the current tenant. Supports filtering and sorting.
Create Project POST /projects Creates a new project within the current tenant's scope.
Get Project GET /projects/{project_id} Retrieves a single project by its ID.
Update Project PATCH /projects/{project_id} Partially updates a project's details (e.g., name, status).
Delete Project DELETE /projects/{project_id} Deletes a project and its associated data.

Data Flow & API Contracts:

  • GET /projects?status=active&sort=start_date_desc&page=1&limit=20

    • Description: This endpoint showcases how we'll handle real-world data retrieval.
    • Permissions: User needs can_view_projects permission.
    • Data Flow:
      1. Auth dependency validates the JWT and provides the tenant_id.
      2. RBAC dependency checks for can_view_projects.
      3. The service layer queries the projects table with WHERE tenant_id = ?.
      4. Filtering (status='active'), sorting (ORDER BY start_date DESC), and pagination (LIMIT 20 OFFSET 0) are applied to the query.
      5. The response includes the list of projects and pagination metadata.
    • Success Response (200 OK):
      {
        "items": [
          {
            "id": "proj_uuid_1",
            "name": "Skyscraper Foundation",
            "start_date": "2024-10-01",
            "status": "active"
          }
        ],
        "total": 1,
        "page": 1,
        "limit": 20
      }
      
  • POST /projects

    • Permissions: User needs can_create_projects permission.
    • Request Body:
      {
        "name": "New Office Building",
        "description": "20-story office building construction.",
        "start_date": "2025-01-15"
      }
      
    • Data Flow: The service layer validates the payload, creates a new project record, and associates it with the tenant_id from the JWT context.
    • Success Response (201 Created):
      {
        "id": "proj_uuid_2",
        "name": "New Office Building",
        "description": "20-story office building construction.",
        "start_date": "2025-01-15",
        "status": "pending",
        "created_at": "2024-09-15T10:00:00Z"
      }
      
    • Error Responses: 403 Forbidden (insufficient permissions), 422 Unprocessable Entity (validation error).

2. Tasks Resource

Tasks are children of projects. This relationship is reflected in the API design. We'll use a nested route for creating/listing tasks within a project context and flat routes for operating on a specific task.

Action Method & Endpoint Description
List Tasks in Project GET /projects/{project_id}/tasks Retrieves a list of tasks for a specific project.
Create Task in Project POST /projects/{project_id}/tasks Creates a new task within a specific project.
Get Task GET /tasks/{task_id} Retrieves a single task by its ID.
Update Task PATCH /tasks/{task_id} Partially updates a task (e.g., status, assignee).
Delete Task DELETE /tasks/{task_id} Deletes a task.

Data Flow & API Contracts:

  • POST /projects/{project_id}/tasks

    • Permissions: User needs can_create_tasks permission.
    • Data Flow:
      1. The service first verifies that {project_id} belongs to the current user's tenant to prevent cross-tenant data creation.
      2. A new task record is created with a foreign key to {project_id} and the tenant_id.
    • Request Body:
      {
        "name": "Lay the concrete foundation",
        "due_date": "2025-02-01",
        "assignee_id": "user_uuid_xyz" // Optional: ID of a user in the same tenant
      }
      
    • Success Response (201 Created): The full task object.
  • PATCH /tasks/{task_id}

    • Permissions: User needs can_edit_tasks. If changing assignee, perhaps can_assign_tasks. This demonstrates how permissions can get granular.
    • Data Flow:
      1. The service fetches the task by {task_id}, ensuring it belongs to the user's tenant_id.
      2. The provided fields are updated.
    • Request Body:
      {
        "status": "in_progress" // Example of updating a task's state
      }
      
    • Success Response (200 OK): The updated task object.

3. Milestones Resource

Milestones are significant checkpoints in a project. Their API design mirrors that of tasks.

Action Method & Endpoint Description
List Milestones GET /projects/{project_id}/milestones Retrieves milestones for a specific project.
Create Milestone POST /projects/{project_id}/milestones Creates a new milestone for a project.
Get Milestone GET /milestones/{milestone_id} Retrieves a single milestone by its ID.
Update Milestone PATCH /milestones/{milestone_id} Updates a milestone's details.
Delete Milestone DELETE /milestones/{milestone_id} Deletes a milestone.

Data Flow & API Contracts:

  • The contracts for milestones are analogous to tasks. For example, POST /projects/{project_id}/milestones would create a milestone linked to that project.
  • A key feature for milestones might be automatic status updates based on task completion. This logic would reside in the service layer, triggered when a task's status changes. The API contract itself remains simple.
  • Request Body for POST:
    {
      "name": "Foundation Phase Complete",
      "due_date": "2025-03-01"
    }
    
  • Success Response (201 Created): The new milestone object.

A Design That Breathes: Best Practices

This design follows several best practices that make an API robust and pleasant to work with.

REST API Design: Endpoints, Resources and Data Patterns

This article from Medium provides a comprehensive workflow for API design. It reinforces the concepts we've applied today.

Please review the following sections: The summarized workflow provides a great checklist for our process. Read about specifying resource attributes, which corresponds to our request/response body definitions. The section on nested resources justifies our hybrid approach for tasks (nested for listing, flat for single operations). Finally, review the section on implementing pagination, filtering, and sorting, which we've built into our GET endpoints for collections.

By defining clear resources, using standard HTTP methods, and planning for real-world use cases like pagination, we create an API that is not only functional but also scalable and maintainable.


Conclusion

We have successfully created a detailed low-level design for our project management service. This design translates our business requirements into concrete API contracts that are ready for implementation. We have defined the resources, their endpoints, the shape of the data they exchange, and the underlying data flows that respect our multi-tenancy and RBAC rules.

Key Takeaways:

  • Resource-Oriented Design: We mapped our core database entities (projects, tasks, milestones) to RESTful resources using plural nouns.
  • Hierarchical & Flat Routes: We used nested routes (/projects/{id}/tasks) to reflect parent-child relationships for listing/creation and flat routes (/tasks/{id}) for efficient operations on individual resources.
  • Tenant Isolation by Default: All API data flows are implicitly scoped to the tenant_id extracted from the JWT, ensuring strict data separation.
  • Practical API Contracts: Our list endpoints are designed with pagination, sorting, and filtering from the start, which is essential for performance and a good user experience.

Preview of the Next Lesson:
With the system architecture and low-level API design complete for our core services, we have a solid blueprint. It's time to move from design to the database. In the next module, we will begin Phase 2: Multi-Tenant Database Design, where we'll translate these designs into a concrete PostgreSQL schema, starting with the tables for tenants and subscriptions.