Todo App Documentation

Version 1.x.x | Last updated on - 05 Jun 2025

Welcome to the Todo App documentation for version 1.x.x. This documentation will tell you how trishul api is used to build todo app.

For the latest updates, please visit GitHub repository.

Last updated on: 05 Jun 2025

For any issues or contributions, please refer to our GitHub account.

Step 1 (Installation)

In step 1, i am going to start with how i used composer to create a blank project.

I opened my workspace, and then opened the project in terminal/command prompt. I verified that composer and latest php version is already installed in my system. If composer and php is not installed in your system, you must install them before proceeding to the next step.

To see whether composer is installed in your system. You can run composer command in your terminal. To check PHP is installed, run php -v

I checked that everything is installed in my system. I run the command composer create-project trishulapi/framework todos. I see some logs in the console and found that composer is creating blank project after creating todos directory in the current directory.

cd todos to navigate the terminal to todos directory and then I run another command to update all required dependencies of this framework.

composer update updates all the required dependencies.

Step 2 (Run the Project)

After I created and updated all the dependencies. I run the command php -S localhost:8000 and wonder i see that my application is live on localhost:8000

Then I opened my application in VS Code. And analyzed what is the default project structure.

  • src
    • logs
  • .env
  • readme.md
  • .gitignore
  • index.php
  • composer.json
  • composer.lock
  • .htaccess

After watching the project structure, I reminded the Trishul API Documentation and understood that I need to keep and manage my code in src directory only.

So I created many sub directories like

  • Controllers
  • Middlewares
  • Dto
  • Services
  • Models
  • logs
  • Utils.php
  • Route.php

Step 3 (Setting .env properties)

My vision was clear that I am building backend APIs for todo application. So to keep the users, todos data, I would required database connection. TrishulAPI comes with inbuilt micro ORM and all database related properties are automatically picked up from .env file. So I read the documentation and visited the forum and found that it has many default properties to manage the application.

  • DB_HOST
  • DB_PORT
  • DB_USERNAME
  • DB_PASSWORD
  • DB_NAME
  • DB_TYPE
  • APP_NAME
  • APP_URL
  • HOST_PATH
  • ALLOWED_DOMAINS
  • ENABLE_SWAGGER
  • SHOW_STACKTRACE_IN_EXCEPTION
  • LOG_DIR

These all properties are by default added in .env file. I updated all these properties to connect with my mysql database.

DB_HOST=localhost
DB_PORT=3306
DB_USERNAME=shyam
DB_PASSWORD=Pass@123
DB_NAME=mydb
DB_TYPE=mysqL
APP_NAME=My App 
APP_URL=http://localhost:8000
HOST_PATH=/
ALLOWED_DOMAINS=["localhost:8000"]
#either 0 or 1 for ENABLE_SWAGGER DOCUMENTATION, If you set 0 it will not generate swagger documentation which will help you in production. 
ENABLE_SWAGGER=0
# if you want to see stacktrace, you can set this value to 1 , Keep this 0 for production.
SHOW_STACKTRACE_IN_EXCEPTION=0
LOG_DIR=logs
            

Step 4 (Creating Models)

Now my backend is connected with database. I need to create a model (Class) which represents the table in database. So I created first model in my Models package.

namespace App\Models;
use TrishulApi\Core\Data\Model;
class User extends Model
{
    public static string $table_name = 'users';
    public static string $primary_key = 'user_id';
   
}

I need to specify two properties of parent class TrishulApi\Core\Data\Model\Model

  • public static string $table_name;
  • public static string $primary_key

These two properties tells the Model class about the table_name and primary_key of that table name. In my case I connected the User model to my users table in my database. Primary key of my users table is user_id.

Similary I created Model for Todos.

namespace App\Models;

use TrishulApi\Core\Data\Model;
class Todo extends Model
{
    public static string $table_name = 'todos';
    public static string $primary_key = 'todo_id';

    
}

Note: TrishulApi's Model class provides a lot of inbuilt functions to fetch the data from database.

Step 5 (Explore Model Functions)

After I created all the required models. I wanted to explore what all functions/methods are available in Model class which I can use to interact with database to do operations on database. TrishulApi\Core\Data\Model class provides detailed information on available functions.

After I watched all available functions in Model class. I created Service class for both Models (User and Todo) and used some functions in these services.

UserService.php

namespace App\Services;

use App\Models\User;
use Exception;
use TrishulApi\Core\Enums\HttpStatus;
use TrishulApi\Core\Exception\ResourceNotFoundException;
use TrishulApi\Core\Http\Request;
use TrishulApi\Core\Http\Response;

class UserService
{

    public function __construct()
    {
    }   
    
    /**
     * Get user by ID.
     *
     * @param int $userId
     * @return array|null
     */
    public function getUserById($userId)
    {
        // Logic to retrieve user by ID from the database
        // This is a placeholder implementation
        return User::find(conditions: ["user_id = " . $userId])[0];
    }

    /**
     * Create a new user.
     *
     * @param array $userData
     * @return bool
     */
    public function createUser($user): array
    {
        
       $user = ["name" => $user->name, "username" => $user->username, "password" => $user->password];
        $id = User::create($user);
        return User::find(conditions:["user_id = $id"])[0];
    }

    public function getAllUsers()
    {
        
        return User::all();
    }


    public function login($username, $password)
    {
       
        $user = User::find(conditions: ["username = '$username' AND password = '$password'"]);
        
        if (count($user) > 0) {
            return $user[0];
        } else {
            return false;
        }
    }

    public function deleteById($userId)
    {
        if($userId == null)
        {
            throw new Exception("userId is required.");
        }

        $user = User::find(["user_id = $userId"])[0];
        if($user == null)
        {
            throw new ResourceNotFoundException("User not found for this id: ".$userId);
        }

        User::delete($userId);
    }

    public function updateUser($userId, $data)
    {
        if(!isset($data->username) || !isset($data->name) || !isset($data->password))
        User::update($userId, [
            "username"=>$data->username,
            "name"=>$data->name,
            "password"=>$data->password
        ]);
    }

TodoService.php


namespace App\Services;

use App\Models\Todo;
use App\Models\User;
use App\Utils;
use Exception;
use TrishulApi\Core\Enums\HttpStatus;
use TrishulApi\Core\Exception\ResourceNotFoundException;
use TrishulApi\Core\Exception\UnauthorizedException;
use TrishulApi\Core\Http\Request;
use TrishulApi\Core\Http\Response;

class TodoService
{
    private Request $request;
    // This class will handle the business logic for Todo operations.
    // It will interact with the Todo model and perform CRUD operations.
    public function __construct(Request $request)
    {
        $this->request = $request;
    }
   
    public function getAllTodos():Response
    {
        return Response::json(HttpStatus::OK, Todo::all());
    }

    public function getTodoById($todoId)
    {
        
        $todo = Todo::find(conditions:["todo_id = $todoId"])[0];
        if (!$todo) {
            throw new ResourceNotFoundException("Todo not found for this id: ".$todoId);
        }
        return $todo;
    }

    public function createTodo(array $todo)
    {
        if (empty($todo['title']) || empty($todo['created_by'])) {
            return Response::json(HttpStatus::BAD_REQUEST, ['message' => 'Title and created_by are required']);
        }
        $id = Todo::create($todo);
        return Todo::find(["todo_id = $id"])[0];
    }

    public function updateTodo($todoId, $data)
    {
        
        Todo::update($todoId, [
            'title' => $data->title,
            'created_on' => date('Y-m-d H:i:s'),
            'created_by' => $data->created_by
        ]);
        
    }

    public function getTodosByUserId($userId)
    {
        if (!$userId) {
            throw new Exception("User Id is required.");
        }
        $todos = Todo::filter(["created_by = $userId"]);
        
        return $todos;
    }

    public function deleteTodo($todoId)
    {
       
        $todo = Todo::find(conditions:["todo_id = $todoId"])[0];
        if (!$todo) {
            throw new ResourceNotFoundException("Todo not found for this todo Id");
        }
        
        Todo::delete($todoId);
    }

Step 6 (Creating Controllers)

After Everything (Models, Services (to handle to business logic)) I created controllers for each Model so that routes can easily configured and call the controller methods for the output.

UserController.php


namespace App\Controllers;

use App\Models\User;
use App\Services\UserService;
use App\Utils;
use Exception;
use LDAP\Result;
use TrishulApi\Core\Enums\HttpStatus;
use TrishulApi\Core\Exception\UnauthorizedException;
use TrishulApi\Core\Http\Response;
use TrishulApi\Core\Http\Request;
class UserController
{

    private UserService $userService;
    private Request $request;
    public function __construct(UserService $userService, Request $request)
    {
        $this->userService = new UserService($request);
        $this->request = $request;
    }
    /**
     * Handle user login.
     *
     * @return Response
     */
    public function login(): Response
    {
        // Logic to handle user login
        $username = $this->request->body()->get('username');
        $password = $this->request->body()->get('password');
        if (empty($username) || empty($password)) {
            return Response::json(HttpStatus::BAD_REQUEST, ["message" => "Username and password are required"]);
        }
        $user = $this->userService->login($username, $password);
        if (!$user) {
            return Response::json(HttpStatus::UNAUTHORIZED, ["message" => "Invalid credentials"]);
        }
        // This is a placeholder implementation
        return Response::json(HttpStatus::OK, ["message" => "User logged in successfully", "user" => $user]);
    }


    public function saveUser(): Response
    {
        // Logic to save user data
        $userData = $this->request->body()->data();
        if (empty($userData)) {
            return Response::json(HttpStatus::BAD_REQUEST, ["message" => "User data is required"]);
        }
        $user = $this->userService->createUser($userData);
        if (!$user) {
            return Response::json(HttpStatus::INTERNAL_SERVER_ERROR, ["message" => "Failed to create user"]);
        }
        return Response::json(HttpStatus::CREATED, ["message" => "User created successfully", "user" => $user]);
    }

    /**
     * Handle user logout.
     *
     * @return Response
     */
    public function logout(): Response
    {
        // Logic to handle user logout
        // This is a placeholder implementation
        return Response::json(HttpStatus::OK, ["message" => "User logged out successfully"]);
    }


    public function deleteUser()
    {
        $user = Utils::get_user_from_basic_auth();
        if($user == null)
        {
            throw new UnauthorizedException("Not authorized");
        }

        $userId = $this->request->path()->get('userId');
        if($userId == null)
        {
            throw new Exception("userId is required.");
        }

        if ($user['user_id'] != $userId) {
            throw new Exception("You are not the onwer of this account.");
        }

        $this->userService->deleteById($userId);
        return Response::json(HttpStatus::OK, ["message"=>"User deleted successfully."]);

    }


    public function getLoggedInUser()
    {
        $user = Utils::get_user_from_basic_auth();
        if($user == null)
        {
            throw new UnauthorizedException("Not authorized");
        }

        return Response::json(HttpStatus::OK, $this->userService->getUserById($user['user_id']));
    }

    public function getUserById()
    {
        $userId = $this->request->path()->get('userId');
        if($userId == null)
        {
            throw new Exception("User id is required.");
        }

        $user = $this->userService->getUserById($userId);
        return Response::json(HttpStatus::OK, ["user_id"=>$user['user_id'], "username"=>$user['username'], "name"=>$user['name']]);
    }


    public function updateUser()
    {
        $userId = $this->request->path()->get('userId');
        if($userId == null)
        {
            throw new Exception("User id is required.");
        }

        $user = Utils::get_user_from_basic_auth();
        if($user == null)
        {
            throw new Exception("Not authorized");
        }

        if($user['user_id'] != $userId)
        {
            throw new Exception("You are not owner of this account.");
        }

        $userData = $this->request->body()->data();
        if (empty($userData)) {
            return Response::json(HttpStatus::BAD_REQUEST, ["message" => "User data is required"]);
        }

        $savedUser = $this->userService->getUserById($userId);

        if($savedUser == null){
            throw new Exception("User not found for id: ".$userId);
        }

        $this->userService->updateUser($userId, $userData);
        return Response::json(HttpStatus::OK, ["message"=>"User updated successfully."]);
    }

    public function getAllUsers()
    {
        $users = $this->userService->getAllUsers();
        foreach ($users as &$user) {
            unset($user['password']); // Remove password from the response
        }
        return Response::json(HttpStatus::OK, $users);
    }

Step 7 (Creating Routes)

After I created controllers for each Model. I created routes to make api endpoints. TrishulApi\Core\Http\Router class is used to create routes for application.

I created new file Routes.php and added all the routes in this file. Code is here:


use App\Controllers\UserController;
use App\Controllers\TodoController;
use App\Dto\CreateUserDto;
use App\Dto\TodoDto;
use App\Dto\TodoRequestDto;
use App\Dto\UserDto;
use App\Middlewares\BasicAuthMiddleware;
use TrishulApi\Core\Http\RequestType;
use TrishulApi\Core\Http\Router;

 $routes = [

    Router::parent("/users", [
        Router::children(RequestType::GET, "/", UserController::class."@getAllUsers", response_type:[UserDto::class], security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::GET, "/{userId}", UserController::class."@getUserById",security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::GET, "/1/me", UserController::class."@getLoggedInUser",security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::POST, "/", UserController::class."@saveUser",requestBody:[CreateUserDto::class]),
        Router::children(RequestType::PUT, "/{userId}", UserController::class."@updateUser",security:["basicAuth", "bearerAuth",], requestBody:UserDto::class),
        Router::children(RequestType::DELETE, "/{userId}", UserController::class."@deleteUser",security:["basicAuth", "bearerAuth",]),
    ],middlewares:[BasicAuthMiddleware::class], except:["/users"=>RequestType::POST])
    ,

    Router::parent("/todos", [
        Router::children(RequestType::GET, "/", TodoController::class."@getAllTodos", response_type:[TodoDto::class], security: ["basicAuth", "bearerAuth",]),
        Router::children(RequestType::GET, "/{todoId}", TodoController::class."@getTodoById",security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::POST, "/", TodoController::class."@createTodo", requestBody:[TodoRequestDto::class],security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::PUT, "/{todoId}", TodoController::class."@updateTodo", requestBody:[TodoDto::class],security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::DELETE, "/{todoId}", TodoController::class."@deleteTodo",security:["basicAuth", "bearerAuth",]),
        Router::children(RequestType::GET, "/user/{userId}", TodoController::class."@getTodosByUserId", response_type:[TodoDto::class],security:["basicAuth", "bearerAuth",]),
    ])
    ];

I created two parent routes /users and /todos and then I grouped the related routes under these parent route.

Step 8 (Securing Endpoints)

Now my APIs are ready to accept traffic. Swagger Documentation is also ready for use on http://localhost:8000/docs. Now I need to work on the security of my APIs. So I will first add only localhost:8000 in the .env ALLOWED_DOMAIN=["localhost:8000"]. This will allow only localhost:8000 to interact with my APIs. After this, I will create Middleware to check the authentication on API Request. So I created AuthMiddlware class to intercept the request for checking authentication.


namespace App\Middlewares;

use App\Models\User;
use App\Services\UserService;
use App\Utils;
use TrishulApi\Core\Http\Request;
use TrishulApi\Core\Http\Response;
use TrishulApi\Core\Middleware\MiddlewareInterface;

class BasicAuthMiddleware implements MiddlewareInterface
{
    private UserService $userService;
    
    public function handle_request($request):Request
    {
        $this->userService = new UserService($request);
        // Check if the request has the Authorization header
        if (!$request->header()->has('Authorization')) {
            throw new \Exception('Authorization header not found', 401);
        }

        // Get the Authorization header value
        $authHeader = $request->header()->get('Authorization');
        if (empty($authHeader)) {
            throw new \Exception('Authorization header is empty', 401);
        }

        // Check if the header starts with 'Basic '
        if (strpos($authHeader, 'Basic ') !== 0) {
            throw new \Exception('Authorization should start with Basic', 401);

        }

        // Decode the base64 encoded credentials
        $credentials = base64_decode(substr($authHeader, 6));
        list($username, $password) = explode(':', $credentials);

        // Validate the credentials
        $user = $this->userService->login($username, $password);
        if (!$user) {
            throw new \Exception('Invalid credentials', 401);
        }

        Utils::set_user_from_basic_auth($user);
        // If credentials are valid, proceed to the next middleware or request handler
        return $request;
    }

    public function handle_response(Response $response):Response
    {
        // This middleware does not modify the response
        return $response;
    }

This checks the request header for authorization and validate the request from database.

Now Everything is ready. My Backend APIs are secured and live for testing. If you want to test these Todo APIs. Check it here. TODO App API Docs