Skip to content

Your First API Endpoint

drawfig edited this page Aug 26, 2026 · 6 revisions

After following the Quickstart Guide, you should have a working mouse-php application. We will now walk through the next step: creating your first API route and using the built-in PDO wrapper to retrieve user data by ID from a database.

Open Squeak

If you don't have the Squeak Shell open, launch it by running the following command in the root directory of your application:

php squeak

Creating a Controller

Inside Squeak, run the following command to spin up the controller scaffolding:

> add-controller

You will be prompted to name the controller. Squeak will automatically format your input into the proper class name convention. For example, entering example will output Example_Controller.

Creating a Model

Next, create the model by running the model scaffolding command in Squeak:

> add-model

Just like the controller, entering example will automatically format the class name to Example_Model.

Writing Your Logic.

This is where you begin writing custom code and where you will first encounter mouse-php's Data Marshal philosophy.

Unlike traditional MVC frameworks that rely on "fat models" and skinny controllers, mouse-php is designed as a data marshal that provides high-speed API endpoints for decoupled frontends. In this architecture:

  • Controllers manage business logic, data formatting, and response processing.
  • Models act as lightweight, high-speed pipelines for delivering data from external sources (such as databases, microservices, etc.) straight to the controller.

To learn more about the reasoning behind these design choices, head over to the Lifecycle and Architecture Design page. Otherwise, let's build out the API route.

Building the Model

Navigate to ./core/models and open Example_Model.php. By default, you will see:

<?php 
namespace models;

class Example_Model {
    
    
}   

Add a public method to handle the database call.

<?php 
namespace models;

class Example_Model {
    
    public function get_user($db, $id) {}
    
}   

Make sure the scope is set to public so it can be called from your controller. We pass both the active database connection object ($db) and the user ID into the method. Passing the database instance ensures we keep a single connection alive through the lifecycle of the request rather than spinning up a new connection for every call.

Now, let's write the database query using mouse-php's built-in PDO wrapper:

<?php 
namespace models;

class Example_Model {
    
    public function get_user($db, $id) {
        $query = "SELECT * FROM users WHERE id = :id";
        $vals = [
            [
                "name" => ":id",
                "value" => $id,
                "type" => "i"
            ]
        ];
        
        try {
            return $db->make_query("select", $query, $vals);
        }
        catch(\Exception $e) {
            return false;
        }
    }
    
}  

This structure applies to standard SELECT queries. For other operations (INSERT, DELETE, etc.), update the query, variables, and the operation type passed to make_query. For select queries, this method returns an array of rows or false on failure; modification queries return the ID of the affected entry.

Building Our Controller

Now that the model is ready, we can construct the controller to fetch the data and structure the response for the frontend. Navigate to ./core/controllers and open Example_Controller.php.

Initially, the file looks like this:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }

}

First, expose the database object to the controller instance so it can be passed down to the model:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }

}

Next, add a public method that matches your route configuration:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }
    
    public function get_user() {}

}

You should also set the scope of this method to public as well to that the mouse-php's routing engine can then execute the request with it. Now that we have our method added to the controller we can begin building out our controller logic, the first step is getting our request data like so:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }
    
    public function get_user() {
        $data = $this->REQUEST_DATA["data"];
    }

}

Now we have the data we need to make our query we need to call the Model we just created:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }
    
    public function get_user() {
        $data = $this->REQUEST_DATA["data"];
        
        $model = new \models\Example_Model();
        $user_raw = $model->get_user($this->DB, $data["id"]);
    }

}

Now, let's handle failure states. Foundational errors like field validation or authentication are caught earlier by the middleware, so by the time execution reaches the controller, we only need to handle downstream failures (such as the database failing to find the record):

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }
    
    public function get_user() {
        $data = $this->REQUEST_DATA["data"];
        
        $model = new \models\Example_Model();
        $user_raw = $model->get_user($this->DB, $data["id"]);
        
        if($user_raw && sizeof($user_raw) > 0) {
            
        }
        else {
            $this->RESP->send(["api_message" => "Database failed to find requested User"], "Generic", $this->REQUEST_DATA["request_tag"], false, false);
        }
    }

}

Finally, let's write out the response for a successful database lookup:

<?php
namespace controllers;

class Example_Controller {
    public $REQUEST_DATA;
    public $RESP;
    public $DB;

    public function __construct($db, $sqlite, $request_data) {
        $this->REQUEST_DATA = $request_data;
        $this->DB = $db;
        if($db !== 1) {
            $this->RESP = new \utils\Response_Handler();
        }
    }
    
    public function get_user() {
        $data = $this->REQUEST_DATA["data"];
        
        $model = new \models\Example_Model();
        $user_raw = $model->get_user($this->DB, $data["id"]);
        
        if($user_raw && sizeof($user_raw) > 0) {
            $out = $user_raw[0];
            
            $this->RESP->send(["response" => $out], "Generic", $this->REQUEST_DATA["request_tag"], false);
        }
        else {
            $this->RESP->send(["api_message" => "Database failed to find requested User"], "Generic", $this->REQUEST_DATA["request_tag"], false, false);
        }
    }

}

Note: We are using the Response_Handler utility class to format the response. If you'd like to learn more about the Response Handler, check out the Response Handler documentation.

Creating A Route

There are two ways to add routes in mouse-php. The recommended approach is using Squeak's terminal UI, but understanding manual configuration is vital for grasping how the router functions.

In Squeak, run:

> add-route

Follow the interactive prompts to name the URL and configure your route settings. Once completed, you can verify your routes by running:

> show-routes

Your new endpoint will now be reachable on your server, prefixed with /api (e.g., http://127.0.0.1:9000/api/example)).

Manual Route Configuration

If you open Api_Routes.php, your route will look like this:

<?php
namespace routes;

class Api_Routes {
    public $ROUTES = [
        '/example' => ['class' => 'Example_Controller', 'method' => 'get_user', 'type' => 'POST'],
    ];
}

To add routes manually, open Api_Routes.php and append an element to the $ROUTES array. If you need dynamic URL parameters, wrap them in colons (for example: /api/user/get/:id:). The associated array defines the target controller class, execution method, and REST verb.

Working Route

Congratulations! You have completed your first API endpoint in mouse-php and are ready to build out your application. For more details, check out the Squeak Command Guide and the Request Lifecycle documentation.

Calling this from your browser

If you'd like to easily use this API endpoint in your front end such as React, Vue, or Angular we'd recommend using the official colby-jack npm package. If you'd like to learn more you can read it in the Client Ecosystem & Integration section of the docs.

Clone this wiki locally