Skip to content

aishikasaha/simple-task-api

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Task Management API - Scala & Play Framework

A complete REST API built with Scala, Play Framework, and Slick ORM demonstrating modern functional programming concepts and professional backend development patterns.

🎯 What You'll Learn

This project covers essential Scala and backend development concepts:

  • Scala Fundamentals: Case classes, sealed traits, pattern matching, Option/Either types
  • Functional Programming: Immutable data structures, pure functions, type safety
  • Play Framework: REST API development, dependency injection, routing
  • Slick ORM: Type-safe database queries, functional database access
  • Database Design: Schema management, evolutions, relationships
  • API Design: RESTful endpoints, JSON serialization, error handling
  • Testing: Comprehensive API testing strategies

πŸ“‹ Table of Contents

  1. Project Overview
  2. Setup Instructions
  3. Project Structure
  4. Core Concepts
  5. API Documentation
  6. Code Flow Explanation
  7. Scala Concepts Used
  8. Testing Guide
  9. Troubleshooting
  10. Learning Resources

🎯 Project Overview

What This API Does

The Task Management API allows you to:

  • Create, read, update, and delete tasks
  • Set task priorities (Low, Medium, High)
  • Mark tasks as completed
  • Set due dates for tasks
  • Filter tasks by status, priority, and due dates
  • Get task statistics and analytics

Technology Stack

  • Language: Scala 2.13
  • Framework: Play Framework 2.9
  • Database: MySQL 8.0 (via Docker)
  • ORM: Slick 3.4
  • Build Tool: SBT (Scala Build Tool)
  • JSON: Play JSON library
  • Dependency Injection: Google Guice

πŸš€ Setup Instructions

Prerequisites

  1. Java 11+: Download from Oracle or use OpenJDK
  2. SBT: Install from scala-sbt.org
  3. Docker: Install from docker.com
  4. VS Code (recommended): With Scala (Metals) extension

Quick Start

  1. Clone/Create the Project

    mkdir task-management-api
    cd task-management-api
  2. Set Up Database (MySQL via Docker)

    docker run --name task-mysql \
      -e MYSQL_ROOT_PASSWORD=rootpass \
      -e MYSQL_DATABASE=taskapi \
      -e MYSQL_USER=taskuser \
      -e MYSQL_PASSWORD=taskpass \
      -p 3306:3306 \
      -d mysql:8.0
  3. Create Project Files

    Copy all the project files shown in the Project Structure section below.

  4. Run the Application

    sbt clean
    sbt compile
    sbt run
  5. Test the API

    Visit: http://localhost:9000/api/tasks


πŸ“ Project Structure

task-management-api/
β”œβ”€β”€ build.sbt                          # Build configuration and dependencies
β”œβ”€β”€ project/
β”‚   β”œβ”€β”€ build.properties              # SBT version
β”‚   └── plugins.sbt                   # SBT plugins (Play Framework)
β”œβ”€β”€ conf/
β”‚   β”œβ”€β”€ application.conf               # Application configuration
β”‚   β”œβ”€β”€ routes                        # URL routing definitions
β”‚   └── evolutions/
β”‚       └── default/
β”‚           └── 1.sql                  # Database schema migration
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ controllers/
β”‚   β”‚   β”œβ”€β”€ HomeController.scala       # Basic endpoints (health, home)
β”‚   β”‚   └── TaskController.scala       # Task API endpoints
β”‚   β”œβ”€β”€ models/
β”‚   β”‚   └── Task.scala                 # Data models and DTOs
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   └── TaskService.scala          # Business logic layer
β”‚   β”œβ”€β”€ repositories/
β”‚   β”‚   └── TaskRepository.scala       # Database access layer
β”‚   └── Module.scala                   # Dependency injection configuration
└── test/
    └── (test files)

Key Files Explained

  • build.sbt: Defines project dependencies, Scala version, and build settings
  • conf/application.conf: Database connections, Play Framework configuration
  • conf/routes: Maps HTTP requests to controller methods
  • models/Task.scala: Data structures representing your domain
  • controllers/TaskController.scala: Handles HTTP requests and responses
  • services/TaskService.scala: Contains business logic and validation
  • repositories/TaskRepository.scala: Database operations using Slick ORM

🧠 Core Concepts

1. MVC Architecture

HTTP Request β†’ Controller β†’ Service β†’ Repository β†’ Database
                    ↓
HTTP Response ← Controller ← Service ← Repository ← Database
  • Controller: Handles HTTP requests/responses, JSON serialization
  • Service: Business logic, validation, orchestration
  • Repository: Database operations, query building
  • Model: Data structures and domain objects

2. Dependency Injection

@Singleton
class TaskController @Inject()(
  val controllerComponents: ControllerComponents,
  taskService: TaskService  // ← Injected dependency
)(implicit ec: ExecutionContext) extends BaseController

Dependencies are automatically provided by the DI framework.

3. Functional Programming Principles

  • Immutability: All data structures are immutable by default
  • Pure Functions: Functions without side effects
  • Type Safety: Compile-time error checking
  • Pattern Matching: Safe conditional logic

🌐 API Documentation

Base URL

http://localhost:9000

Authentication

None required for this demo API.

Content Type

All POST/PUT requests expect Content-Type: application/json


πŸ“‹ Task Endpoints

GET /api/tasks

Get all tasks

Response:

{
  "status": "success",
  "data": [
    {
      "id": 1,
      "title": "Learn Scala fundamentals",
      "description": "Study case classes, pattern matching",
      "completed": false,
      "priority": 3,
      "dueDate": "2025-06-24T10:00:00",
      "createdAt": "2025-06-17T18:30:00",
      "updatedAt": "2025-06-17T18:30:00"
    }
  ],
  "count": 1
}

GET /api/tasks/{id}

Get specific task by ID

Example: GET /api/tasks/1

POST /api/tasks

Create a new task

Request Body:

{
  "title": "New Task",
  "description": "Task description",
  "priority": 2,
  "completed": false,
  "dueDate": "2025-06-25T10:00:00"
}

Response:

{
  "status": "success",
  "data": {
    "id": 5,
    "title": "New Task",
    "description": "Task description",
    "completed": false,
    "priority": 2,
    "dueDate": "2025-06-25T10:00:00",
    "createdAt": "2025-06-17T18:35:00",
    "updatedAt": "2025-06-17T18:35:00"
  }
}

PUT /api/tasks/{id}

Update an existing task

Request Body: Same as POST

DELETE /api/tasks/{id}

Delete a task

Response:

{
  "status": "success",
  "message": "Task deleted"
}

PATCH /api/tasks/{id}/complete

Mark task as completed


πŸ” Filter Endpoints

GET /api/tasks/priority/{priority}

Get tasks by priority level

  • 1 = Low priority
  • 2 = Medium priority
  • 3 = High priority

Example: GET /api/tasks/priority/3

GET /api/tasks/completed

Get all completed tasks

GET /api/tasks/pending

Get all pending (incomplete) tasks

GET /api/tasks/overdue

Get all overdue tasks


πŸ“Š Analytics Endpoints

GET /api/tasks/stats

Get task statistics

Response:

{
  "status": "success",
  "data": {
    "total": 10,
    "completed": 3,
    "pending": 7,
    "high_priority": 2,
    "medium_priority": 5,
    "low_priority": 3,
    "overdue": 1
  }
}

πŸ₯ Health Endpoints

GET /health

API health check

GET /

Welcome message


πŸ”„ Code Flow Explanation

Let's trace through what happens when you make a request:

Example: POST /api/tasks

1. Request Routing (conf/routes)

POST /api/tasks controllers.TaskController.createTask

Play Framework maps the HTTP request to the controller method.

2. Controller (TaskController.scala)

def createTask() = Action.async(parse.json) { implicit request: Request[JsValue] =>
  request.body.validate[TaskInput] match {
    case JsSuccess(taskInput, _) =>
      taskService.createFromInput(taskInput).map {
        case Right(createdTask) => 
          Created(Json.obj("status" -> "success", "data" -> createdTask))
        case Left(error) => 
          BadRequest(Json.obj("status" -> "error", "message" -> error))
      }
    // ... error handling
  }
}

What happens:

  • Parses JSON request body
  • Validates JSON structure against TaskInput model
  • Calls service layer for business logic
  • Returns appropriate HTTP response

3. Service Layer (TaskService.scala)

def createFromInput(taskInput: TaskInput): Future[Either[String, Task]] = {
  TaskInput.validate(taskInput) match {
    case Left(error) => Future.successful(Left(error))
    case Right(validTaskInput) =>
      val task = TaskInput.toTask(validTaskInput)
      taskRepository.create(task).map(Right(_))
  }
}

What happens:

  • Validates business rules (title not empty, etc.)
  • Converts DTO to domain model
  • Calls repository for database operations
  • Returns Either[Error, Success] for functional error handling

4. Repository Layer (TaskRepository.scala)

def create(task: Task): Future[Task] = {
  val now = LocalDateTime.now()
  val taskWithTimestamps = task.copy(createdAt = now, updatedAt = now)
  
  val insertQuery = tasks returning tasks.map(_.id) into ((task, id) => task.copy(id = id))
  db.run(insertQuery += taskWithTimestamps)
}

What happens:

  • Sets timestamps automatically
  • Builds type-safe SQL query using Slick
  • Executes query and returns the created task with generated ID

5. Database (MySQL)

INSERT INTO tasks (title, description, completed, priority, due_date, created_at, updated_at) 
VALUES ('New Task', 'Description', FALSE, 2, '2025-06-25 10:00:00', NOW(), NOW());

The database stores the task and returns the generated ID.


πŸŽ“ Scala Concepts Used

1. Case Classes (Immutable Data)

case class Task(
  id: Option[Long] = None,
  title: String,
  description: Option[String] = None,
  completed: Boolean = false,
  priority: Priority = Priority.Low,
  dueDate: Option[LocalDateTime] = None,
  createdAt: LocalDateTime = LocalDateTime.now(),
  updatedAt: LocalDateTime = LocalDateTime.now()
)

Benefits:

  • Automatic equals, hashCode, toString
  • Immutable by default
  • Pattern matching support
  • copy method for updates

2. Sealed Traits (Type-Safe Enums)

sealed trait Priority {
  def value: Int
  def name: String
}

object Priority {
  case object Low extends Priority {
    val value = 1
    val name = "Low"
  }
  
  case object Medium extends Priority {
    val value = 2
    val name = "Medium"
  }
  
  case object High extends Priority {
    val value = 3
    val name = "High"
  }
}

Benefits:

  • Compiler ensures exhaustive pattern matching
  • Type-safe alternatives to magic numbers
  • Cannot be extended outside the file

3. Option Type (Null Safety)

// Instead of nullable fields, use Option
description: Option[String] = None

// Safe usage
task.description match {
  case Some(desc) => println(s"Description: $desc")
  case None => println("No description")
}

// Or with functional methods
task.description.map(_.toUpperCase).getOrElse("NO DESCRIPTION")

4. Either Type (Error Handling)

def validate(task: Task): Either[String, Task] = {
  if (task.title.trim.isEmpty) {
    Left("Task title cannot be empty")  // Error case
  } else {
    Right(task)  // Success case
  }
}

// Usage
validate(task) match {
  case Left(error) => handleError(error)
  case Right(validTask) => processTask(validTask)
}

5. Future (Asynchronous Programming)

def findAll(): Future[Seq[Task]] = {
  db.run(tasks.result)  // Returns Future[Seq[Task]]
}

// Composing Futures
for {
  user <- userRepository.findById(1)
  tasks <- taskRepository.findByUserId(1)
} yield (user, tasks)

6. Pattern Matching

priority match {
  case Priority.High => "Urgent!"
  case Priority.Medium => "Important"
  case Priority.Low => "When possible"
}

// With extraction
task match {
  case Task(_, title, _, true, _, _, _, _) => s"Completed: $title"
  case Task(_, title, _, false, Priority.High, _, _, _) => s"Urgent: $title"
  case _ => "Regular task"
}

7. Implicit Values (Type Classes)

// JSON serialization
implicit val taskFormat: Format[Task] = Json.format[Task]

// Custom column types
implicit val priorityMapper: BaseColumnType[Priority] = MappedColumnType.base[Priority, Int](
  priority => priority.value,
  value => Priority.fromInt(value).getOrElse(Priority.Low)
)

8. Higher-Order Functions

// Functional operations on collections
val highPriorityTasks = tasks.filter(_.priority == Priority.High)
val taskTitles = tasks.map(_.title)
val completedCount = tasks.count(_.completed)

// Function composition
def filterAndSort(tasks: List[Task]): List[Task] = {
  tasks
    .filter(!_.completed)
    .sortBy(_.priority.value)
    .reverse
}

πŸ§ͺ Testing Guide

Manual API Testing

1. Using cURL

Create a task:

curl -X POST http://localhost:9000/api/tasks \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Learn Scala",
    "description": "Study functional programming concepts",
    "priority": 3,
    "completed": false
  }'

Get all tasks:

curl http://localhost:9000/api/tasks

Update a task:

curl -X PUT http://localhost:9000/api/tasks/1 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Master Scala",
    "description": "Deep dive into advanced concepts",
    "priority": 3,
    "completed": true
  }'

2. Using Browser

For GET requests, simply visit:

  • http://localhost:9000/api/tasks
  • http://localhost:9000/api/tasks/stats
  • http://localhost:9000/api/tasks/priority/3

3. Using Postman

  1. Import the following endpoints:

    • GET http://localhost:9000/api/tasks
    • POST http://localhost:9000/api/tasks
    • PUT http://localhost:9000/api/tasks/1
    • DELETE http://localhost:9000/api/tasks/1
  2. Set headers: Content-Type: application/json

  3. Use request body for POST/PUT:

    {
      "title": "Test Task",
      "priority": 2
    }

Comprehensive Test Script

Save this as test-api.sh:

#!/bin/bash

BASE_URL="http://localhost:9000"

echo "πŸ§ͺ Testing Task Management API"

# Test 1: Health check
echo "Testing health endpoint..."
curl -s "$BASE_URL/health" | jq '.'

# Test 2: Get all tasks
echo "Getting all tasks..."
curl -s "$BASE_URL/api/tasks" | jq '.'

# Test 3: Create a task
echo "Creating a new task..."
curl -s -X POST "$BASE_URL/api/tasks" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Test Task",
    "description": "Created by test script",
    "priority": 2
  }' | jq '.'

# Test 4: Get statistics
echo "Getting task statistics..."
curl -s "$BASE_URL/api/tasks/stats" | jq '.'

# Test 5: Filter by priority
echo "Getting high priority tasks..."
curl -s "$BASE_URL/api/tasks/priority/3" | jq '.'

echo "βœ… Tests completed!"

Run with: chmod +x test-api.sh && ./test-api.sh


πŸ›  Troubleshooting

Common Issues

1. Compilation Errors

Error: object is not a member of package

[error] object Priority is not a member of package models

Solution: Check import statements:

import models.{Task, Priority}

2. Database Connection Issues

Error: Database connection failed

Solutions:

# Check Docker container
docker ps | grep mysql

# Restart MySQL container
docker restart task-mysql

# Check MySQL logs
docker logs task-mysql

3. Port Already in Use

Error: Address already in use: bind

Solutions:

# Find process using port 9000
lsof -i :9000

# Kill the process
kill -9 <PID>

# Or use different port
sbt "run 9001"

4. Evolution Errors

Error: Database needs evolution

Solutions:

  • Visit http://localhost:9000 and click "Apply this script now!"
  • Or set play.evolutions.db.default.autoApply = true

5. JSON Parsing Errors

Error: Invalid JSON format

Solution: Ensure proper Content-Type header:

curl -H "Content-Type: application/json" -d '{"title":"Task"}'

Debug Mode

Add these to conf/application.conf for debugging:

# Enable SQL logging
db.default.logSql = true

# Enable detailed error messages
play.debug = true

πŸ“š Learning Resources

Scala Fundamentals

Play Framework

  • Play Framework Documentation: playframework.com
  • Play Framework Tutorials: Step-by-step guides

Slick ORM

  • Slick Documentation: scala-slick.org
  • Slick Examples: Database patterns and best practices

Functional Programming

  • "Functional Programming in Scala": Red book for FP concepts
  • Cats Library: Advanced functional programming patterns

REST API Design

  • RESTful API Guidelines: Best practices for API design
  • HTTP Status Codes: Understanding proper status codes

🎯 Next Steps

Beginner Enhancements

  1. Add User Management: Create user entities and authentication
  2. Add Categories: Organize tasks into categories
  3. Add Search: Full-text search functionality
  4. Add Pagination: Handle large datasets efficiently

Intermediate Enhancements

  1. Add Tests: Unit tests with ScalaTest
  2. Add Validation: Advanced validation with custom validators
  3. Add Caching: Redis integration for performance
  4. Add API Documentation: Swagger/OpenAPI integration

Advanced Enhancements

  1. Add Authentication: JWT tokens and user sessions
  2. Add Authorization: Role-based access control
  3. Add File Uploads: Task attachments functionality
  4. Add Notifications: Email/SMS notifications for due dates
  5. Add Monitoring: Metrics and logging with Akka monitoring
  6. Add Deployment: Docker containerization and cloud deployment

🀝 Contributing

This is an educational project. Feel free to:

  1. Fork the repository
  2. Add new features
  3. Improve documentation
  4. Add tests
  5. Share your learnings

πŸ“„ License

This project is for educational purposes. Feel free to use it as a learning resource.


πŸ“ž Support

If you encounter issues:

  1. Check the Troubleshooting section
  2. Review the error messages carefully
  3. Consult the official documentation for Play Framework and Slick
  4. Practice with simpler examples first

Remember: Learning Scala and functional programming takes time. Start with small steps and gradually build complexity!


Happy Coding! πŸš€


This README covers everything you need to understand and extend this Scala Play Framework API. Use it as a reference as you continue learning Scala and backend development!

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published