JavaScript and APIs¶
The same form class that renders an HTML form can validate JSON from a mobile
app or a fetch() call, and return errors as JSON.
Example¶
An API for the appointments from Getting started,
reusing AppointmentForm:
<?php
declare(strict_types=1);
namespace App\Controllers\Api;
use App\Controllers\BaseController;
use App\Forms\AppointmentForm;
use App\Models\AppointmentModel;
use CodeIgniter\API\ResponseTrait;
use CodeIgniter\HTTP\ResponseInterface;
class Appointments extends BaseController
{
use ResponseTrait;
public function create(): ResponseInterface
{
$form = new AppointmentForm();
// Reads JSON bodies as well as form posts.
if (! $form->validate()) {
return $this->failValidationErrors($form->getErrors());
}
$id = model(AppointmentModel::class)->insert($form->validated());
return $this->respondCreated(['id' => $id] + $form->validated());
}
public function update(int $id): ResponseInterface
{
if (model(AppointmentModel::class)->find($id) === null) {
return $this->failNotFound('No such appointment.');
}
// A genuine PATCH: the form reads its JSON or URL-encoded body.
$form = (new AppointmentForm())->method('patch');
if (! $form->validate()) {
return $this->failValidationErrors($form->getErrors());
}
model(AppointmentModel::class)->update($id, $form->validated());
return $this->respond(['id' => $id] + $form->validated());
}
}
<?php
/** @var \CodeIgniter\Router\RouteCollection $routes */
$routes->post('api/appointments', 'Api\Appointments::create');
$routes->patch('api/appointments/(:num)', 'Api\Appointments::update/$1');
A request:
curl -X POST https://example.com/api/appointments \
-H "Content-Type: application/json" \
-d '{"full_name": "Jane Namuli", "phone": "0772123456", "service": "fitting",
"appointment_date": "2030-01-15", "appointment_time": "10:30"}'
returns 201 Created with the saved data and its id. Invalid data returns
400 with one message per field:
{
"status": 400,
"error": 400,
"messages": {
"phone": "The Phone number field is required.",
"service": "Please choose a valid option for Service."
}
}
How it works¶
- What
validate()reads - With no argument,
validate()reads the current request: the decoded body when theContent-Typeis JSON; otherwise the form post. For a genuinePUT,PATCHorDELETErequest, which PHP does not put in$_POST, it reads the URL-encoded body. - Same rules everywhere
- Rules, the automatic option checking and
validated()behave exactly as for HTML forms. Unknown keys, such as anis_adminin the JSON, never reach your model. - Errors as JSON
getErrors()returnsfield => message, ready forfailValidationErrors()from CodeIgniter'sResponseTrait.
Checking a form from JavaScript¶
To show errors without reloading the page, post the form's data with fetch()
to an endpoint that validates and answers with JSON:
const response = await fetch('/api/appointments', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' },
body: JSON.stringify(Object.fromEntries(new FormData(form))),
});
const result = await response.json(); // result.messages holds the errors
For admin screens that edit schemas, FormSchema::check() and
FormSchema::describe() return JSON-ready data; see
Forms written by admins.
Common mistakes¶
Redirecting after a genuine PATCH without 303. CodeIgniter answers a
PATCH request's redirect with 307, which asks the client to repeat the
PATCH at the new address. APIs should return JSON instead; HTML pages that
redirect after an update should use redirect()->to($url, 303).
Expecting multipart PATCH or PUT bodies to work. PHP only parses
multipart/form-data for POST. Send uploads with POST, or JSON and
URL-encoded data with the other methods.
Forgetting CSRF for calls from your own pages. With CodeIgniter's CSRF
filter on, send the token, for example in an X-CSRF-TOKEN header. Token-based
APIs for other clients are usually excluded from the CSRF filter.
Related¶
- Testing your forms for testing JSON endpoints
- Getting started for the HTML version of these pages