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.
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.
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.
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
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.
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
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
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.
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);
}
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);
}
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.
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