#สร้าง REST API ด้วย CodeIgniter 4 ตั้งแต่เริ่มต้นจนใช้งาน CRUD ได้จริง

CodeIgniter 4 เป็น PHP Framework ที่มีขนาดเล็ก เรียนรู้ไม่ยาก และมีเครื่องมือสำหรับสร้าง Web Application และ REST API มาให้ค่อนข้างครบ เช่น Routing, Model, Database Migration, Validation และ ResourceController

บทความนี้จะสร้าง REST API สำหรับจัดการข้อมูล สินค้า (Products) แบบ CRUD ได้แก่

  • GET /api/v1/products — อ่านสินค้าทั้งหมด
  • GET /api/v1/products/{id} — อ่านสินค้าตาม ID
  • POST /api/v1/products — เพิ่มสินค้า
  • PUT /api/v1/products/{id} — แก้ไขสินค้า
  • PATCH /api/v1/products/{id} — แก้ไขข้อมูลบางส่วน
  • DELETE /api/v1/products/{id} — ลบสินค้า

ตัวอย่างนี้เน้น JSON API และใช้แนวทางของ CodeIgniter 4 รุ่นปัจจุบัน เช่น ResourceController, resource() routing, validateData() และ getValidated() เพื่อให้โค้ดอ่านง่ายและปลอดภัยมากขึ้น

ณ วันที่เขียนบทความ CodeIgniter 4 รุ่นปัจจุบันอยู่ในสาย 4.7 และต้องการ PHP 8.1 ขึ้นไป ควรตรวจสอบ requirement ล่าสุดจากเอกสารทางการก่อนเริ่มโปรเจกต์จริง


#1. สิ่งที่ต้องเตรียม

เครื่องควรมีเครื่องมืออย่างน้อยดังนี้

PHP 8.1+
Composer
MySQL หรือ MariaDB
Git (แนะนำ)
Postman / Bruno / Insomnia หรือ cURL

ตรวจสอบ PHP

php -v

ตรวจสอบ Composer

composer --version

#2. สร้างโปรเจกต์ CodeIgniter 4

วิธีที่แนะนำคือสร้างโปรเจกต์ผ่าน Composer

composer create-project codeigniter4/appstarter ci4-api

เข้าไปยังโฟลเดอร์โปรเจกต์

cd ci4-api

โครงสร้างสำคัญจะมีลักษณะประมาณนี้

ci4-api/
├── app/
│   ├── Config/
│   ├── Controllers/
│   ├── Database/
│   │   └── Migrations/
│   ├── Models/
│   └── Views/
├── public/
├── tests/
├── writable/
├── env
├── spark
└── composer.json

ไฟล์ที่ใช้บ่อยในการสร้าง API คือ

ตำแหน่ง หน้าที่
app/Config/Routes.php กำหนด URL และ HTTP Method
app/Controllers/ รับ Request และสร้าง Response
app/Models/ ติดต่อฐานข้อมูล
app/Database/Migrations/ จัดการโครงสร้างฐานข้อมูล
.env Environment configuration
spark CLI ของ CodeIgniter

#3. ตั้งค่า Environment

CodeIgniter ให้ไฟล์ตัวอย่างชื่อ env

บน macOS/Linux สามารถใช้

cp env .env

บน Windows สามารถคัดลอกไฟล์ env แล้วเปลี่ยนชื่อเป็น .env

จากนั้นเปิด .env และตั้งค่า

CI_ENVIRONMENT = development

app.baseURL = 'http://localhost:8080/'

โหมด development เหมาะกับช่วงพัฒนา เพราะแสดงรายละเอียด Error ได้มากกว่า production

อย่าใช้ development บน Production Server เพราะอาจเปิดเผยข้อมูลภายในระบบ


#4. สร้างฐานข้อมูล

ตัวอย่างนี้ใช้ MySQL และฐานข้อมูลชื่อ ci4_api

CREATE DATABASE ci4_api
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;

แก้ไข .env

database.default.hostname = localhost
database.default.database = ci4_api
database.default.username = root
database.default.password =
database.default.DBDriver = MySQLi
database.default.DBPrefix =
database.default.port = 3306

ค่าจริงขึ้นอยู่กับ MySQL ของแต่ละเครื่อง


#5. ทดสอบการรัน CodeIgniter

CodeIgniter มี Development Server มาให้

php spark serve

โดยปกติจะเปิดที่

http://localhost:8080

หากเห็นหน้า Welcome ของ CodeIgniter แสดงว่าโปรเจกต์พร้อมใช้งานแล้ว


#ส่วนที่ 1: สร้างฐานข้อมูลด้วย Migration

#6. สร้าง Migration สำหรับ Products

สร้าง Migration

php spark make:migration CreateProductsTable

จะได้ไฟล์ใหม่ใน

app/Database/Migrations/

ตัวอย่างไฟล์ Migration

<?php

namespace App\Database\Migrations;

use CodeIgniter\Database\Migration;

class CreateProductsTable extends Migration
{
    public function up()
    {
        $this->forge->addField([
            'id' => [
                'type'           => 'INT',
                'constraint'     => 11,
                'unsigned'       => true,
                'auto_increment' => true,
            ],
            'name' => [
                'type'       => 'VARCHAR',
                'constraint' => 150,
            ],
            'description' => [
                'type' => 'TEXT',
                'null' => true,
            ],
            'price' => [
                'type'       => 'DECIMAL',
                'constraint' => '10,2',
                'default'    => 0,
            ],
            'stock' => [
                'type'       => 'INT',
                'constraint' => 11,
                'default'    => 0,
            ],
            'created_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
            'updated_at' => [
                'type' => 'DATETIME',
                'null' => true,
            ],
        ]);

        $this->forge->addKey('id', true);
        $this->forge->createTable('products');
    }

    public function down()
    {
        $this->forge->dropTable('products');
    }
}

รัน Migration

php spark migrate

ตรวจสอบใน MySQL จะพบตาราง

products

#ส่วนที่ 2: สร้าง Model

#7. สร้าง ProductModel

สร้างไฟล์

app/Models/ProductModel.php

ใส่โค้ด

<?php

namespace App\Models;

use CodeIgniter\Model;

class ProductModel extends Model
{
    protected $table      = 'products';
    protected $primaryKey = 'id';

    protected $returnType = 'array';

    protected $allowedFields = [
        'name',
        'description',
        'price',
        'stock',
    ];

    protected $useTimestamps = true;
}

#ความหมายของค่าที่สำคัญ

$table

protected $table = 'products';

กำหนดชื่อตารางที่ Model จะใช้งาน

$primaryKey

protected $primaryKey = 'id';

กำหนด Primary Key

$allowedFields

protected $allowedFields = [
    'name',
    'description',
    'price',
    'stock',
];

ใช้ควบคุม field ที่อนุญาตให้ insert/update ผ่าน Model

จึงไม่ควรใส่ field ที่ client ไม่ควรแก้ไข เช่น

id
is_admin
role
created_by

โดยไม่จำเป็น


#ส่วนที่ 3: กำหนด REST API Routes

#8. สร้าง Resource Routes

เปิด

app/Config/Routes.php

เพิ่ม

$routes->group('api/v1', static function ($routes) {
    $routes->resource('products', [
        'controller'  => 'Api\ProductsController',
        'placeholder' => '(:num)',
        'except'      => ['new', 'edit'],
    ]);
});

resource() จะสร้าง RESTful routes ให้อัตโนมัติ

เมื่อใช้

$routes->resource('products');

แนวคิดหลักของ route จะเป็น

Method URL Controller Method
GET /products index()
GET /products/{id} show($id)
POST /products create()
PUT/PATCH /products/{id} update($id)
DELETE /products/{id} delete($id)

ในตัวอย่างของเรา URL ถูกครอบด้วย

/api/v1

ดังนั้น API จริงคือ

GET    /api/v1/products
GET    /api/v1/products/1
POST   /api/v1/products
PUT    /api/v1/products/1
PATCH  /api/v1/products/1
DELETE /api/v1/products/1

ตรวจสอบ routes ทั้งหมดได้ด้วย

php spark routes

#ส่วนที่ 4: สร้าง ResourceController

#9. สร้าง ProductsController

สร้างโฟลเดอร์

app/Controllers/Api/

แล้วสร้างไฟล์

app/Controllers/Api/ProductsController.php

เริ่มต้นด้วย

<?php

namespace App\Controllers\Api;

use CodeIgniter\RESTful\ResourceController;

class ProductsController extends ResourceController
{
    protected $modelName = 'App\Models\ProductModel';
    protected $format    = 'json';
}

ResourceController ช่วยให้เราสร้าง REST API ได้ง่ายขึ้น และมี API Response Helper ให้ใช้งาน เช่น

$this->respond()
$this->respondCreated()
$this->respondUpdated()
$this->respondDeleted()
$this->failNotFound()
$this->failValidationErrors()

#10. GET: อ่านสินค้าทั้งหมด

เพิ่ม method

public function index()
{
    $products = $this->model
        ->orderBy('id', 'DESC')
        ->findAll();

    return $this->respond([
        'status' => 'success',
        'data'   => $products,
    ]);
}

ทดสอบ

curl http://localhost:8080/api/v1/products

ตัวอย่าง Response

{
  "status": "success",
  "data": []
}

#11. GET: อ่านสินค้าตาม ID

เพิ่ม

public function show($id = null)
{
    $product = $this->model->find($id);

    if ($product === null) {
        return $this->failNotFound(
            "Product with ID {$id} was not found."
        );
    }

    return $this->respond([
        'status' => 'success',
        'data'   => $product,
    ]);
}

ทดสอบ

curl http://localhost:8080/api/v1/products/1

ถ้าไม่พบข้อมูล API จะตอบ HTTP Status

404 Not Found

#ส่วนที่ 5: Validation และการสร้างข้อมูล

#12. POST: เพิ่มสินค้า

API ควรตรวจสอบข้อมูลก่อนบันทึกเสมอ

เพิ่ม method

public function create()
{
    $data = $this->request->getJSON(true) ?? [];

    $rules = [
        'name'        => 'required|min_length[2]|max_length[150]',
        'description' => 'permit_empty|max_length[1000]',
        'price'       => 'required|decimal|greater_than_equal_to[0]',
        'stock'       => 'required|integer|greater_than_equal_to[0]',
    ];

    if (! $this->validateData($data, $rules)) {
        return $this->failValidationErrors(
            $this->validator->getErrors()
        );
    }

    $validData = $this->validator->getValidated();

    $id = $this->model->insert($validData);

    if ($id === false) {
        return $this->fail(
            $this->model->errors(),
            400
        );
    }

    return $this->respondCreated([
        'status'  => 'success',
        'message' => 'Product created successfully.',
        'data'    => $this->model->find($id),
    ]);
}

จุดสำคัญคือ

$validData = $this->validator->getValidated();

เราเลือกนำ ข้อมูลที่ผ่าน Validation แล้ว ไปใช้ต่อ แทนการนำ Request ทั้งก้อนไปบันทึกโดยตรง


#13. ทดสอบ POST ด้วย cURL

curl -X POST http://localhost:8080/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Mechanical Keyboard",
    "description": "Wireless mechanical keyboard",
    "price": 2590.00,
    "stock": 15
  }'

ตัวอย่าง Response

{
  "status": "success",
  "message": "Product created successfully.",
  "data": {
    "id": "1",
    "name": "Mechanical Keyboard",
    "description": "Wireless mechanical keyboard",
    "price": "2590.00",
    "stock": "15",
    "created_at": "2026-09-15 01:00:00",
    "updated_at": "2026-09-15 01:00:00"
  }
}

HTTP Status ที่เหมาะสมคือ

201 Created

ซึ่ง respondCreated() จัดการให้โดยอัตโนมัติ


#14. กรณี Validation ไม่ผ่าน

ลองส่ง

{
  "name": "",
  "price": -100,
  "stock": -1
}

API จะตอบประมาณ

{
  "status": 400,
  "error": 400,
  "messages": {
    "name": "The name field is required.",
    "price": "The price field must contain a number greater than or equal to 0.",
    "stock": "The stock field must contain a number greater than or equal to 0."
  }
}

ทำให้ Frontend สามารถนำ error ของแต่ละ field ไปแสดงผลได้ง่าย


#ส่วนที่ 6: Update API

#15. PUT / PATCH: แก้ไขสินค้า

สำหรับการแก้ไขข้อมูล เราต้อง

  1. ตรวจสอบว่า Product มีจริง
  2. รับ JSON
  3. ตรวจสอบ field ที่ถูกส่งมา
  4. ใช้เฉพาะ validated data
  5. update
  6. ส่งข้อมูลล่าสุดกลับไป

เพิ่ม method

public function update($id = null)
{
    $product = $this->model->find($id);

    if ($product === null) {
        return $this->failNotFound(
            "Product with ID {$id} was not found."
        );
    }

    $data = $this->request->getJSON(true) ?? [];

    if ($data === []) {
        return $this->fail(
            'No data was supplied.',
            400
        );
    }

    $rules = [
        'name'        => 'if_exist|required|min_length[2]|max_length[150]',
        'description' => 'if_exist|permit_empty|max_length[1000]',
        'price'       => 'if_exist|required|decimal|greater_than_equal_to[0]',
        'stock'       => 'if_exist|required|integer|greater_than_equal_to[0]',
    ];

    if (! $this->validateData($data, $rules)) {
        return $this->failValidationErrors(
            $this->validator->getErrors()
        );
    }

    $validData = $this->validator->getValidated();

    if ($validData === []) {
        return $this->fail(
            'No valid product fields were supplied.',
            400
        );
    }

    if (! $this->model->update($id, $validData)) {
        return $this->fail(
            $this->model->errors(),
            400
        );
    }

    return $this->respondUpdated([
        'status'  => 'success',
        'message' => 'Product updated successfully.',
        'data'    => $this->model->find($id),
    ]);
}

if_exist มีประโยชน์กับ PATCH เพราะ Validation จะตรวจ field เฉพาะเมื่อ field นั้นถูกส่งเข้ามา


#16. ทดสอบ PATCH

curl -X PATCH http://localhost:8080/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{
    "price": 2290,
    "stock": 20
  }'

เราไม่จำเป็นต้องส่ง name และ description ใหม่ทั้งหมด


#ส่วนที่ 7: DELETE API

#17. ลบสินค้า

เพิ่ม

public function delete($id = null)
{
    $product = $this->model->find($id);

    if ($product === null) {
        return $this->failNotFound(
            "Product with ID {$id} was not found."
        );
    }

    if (! $this->model->delete($id)) {
        return $this->fail(
            'Unable to delete product.',
            500
        );
    }

    return $this->respondDeleted([
        'status'  => 'success',
        'message' => 'Product deleted successfully.',
        'data'    => [
            'id' => (int) $id,
        ],
    ]);
}

ทดสอบ

curl -X DELETE http://localhost:8080/api/v1/products/1

#ส่วนที่ 8: Controller ฉบับเต็ม

ไฟล์

app/Controllers/Api/ProductsController.php

สามารถรวมได้ดังนี้

<?php

namespace App\Controllers\Api;

use CodeIgniter\RESTful\ResourceController;

class ProductsController extends ResourceController
{
    protected $modelName = 'App\Models\ProductModel';
    protected $format    = 'json';

    public function index()
    {
        $products = $this->model
            ->orderBy('id', 'DESC')
            ->findAll();

        return $this->respond([
            'status' => 'success',
            'data'   => $products,
        ]);
    }

    public function show($id = null)
    {
        $product = $this->model->find($id);

        if ($product === null) {
            return $this->failNotFound(
                "Product with ID {$id} was not found."
            );
        }

        return $this->respond([
            'status' => 'success',
            'data'   => $product,
        ]);
    }

    public function create()
    {
        $data = $this->request->getJSON(true) ?? [];

        $rules = [
            'name'        => 'required|min_length[2]|max_length[150]',
            'description' => 'permit_empty|max_length[1000]',
            'price'       => 'required|decimal|greater_than_equal_to[0]',
            'stock'       => 'required|integer|greater_than_equal_to[0]',
        ];

        if (! $this->validateData($data, $rules)) {
            return $this->failValidationErrors(
                $this->validator->getErrors()
            );
        }

        $validData = $this->validator->getValidated();

        $id = $this->model->insert($validData);

        if ($id === false) {
            return $this->fail(
                $this->model->errors(),
                400
            );
        }

        return $this->respondCreated([
            'status'  => 'success',
            'message' => 'Product created successfully.',
            'data'    => $this->model->find($id),
        ]);
    }

    public function update($id = null)
    {
        $product = $this->model->find($id);

        if ($product === null) {
            return $this->failNotFound(
                "Product with ID {$id} was not found."
            );
        }

        $data = $this->request->getJSON(true) ?? [];

        if ($data === []) {
            return $this->fail(
                'No data was supplied.',
                400
            );
        }

        $rules = [
            'name'        => 'if_exist|required|min_length[2]|max_length[150]',
            'description' => 'if_exist|permit_empty|max_length[1000]',
            'price'       => 'if_exist|required|decimal|greater_than_equal_to[0]',
            'stock'       => 'if_exist|required|integer|greater_than_equal_to[0]',
        ];

        if (! $this->validateData($data, $rules)) {
            return $this->failValidationErrors(
                $this->validator->getErrors()
            );
        }

        $validData = $this->validator->getValidated();

        if ($validData === []) {
            return $this->fail(
                'No valid product fields were supplied.',
                400
            );
        }

        if (! $this->model->update($id, $validData)) {
            return $this->fail(
                $this->model->errors(),
                400
            );
        }

        return $this->respondUpdated([
            'status'  => 'success',
            'message' => 'Product updated successfully.',
            'data'    => $this->model->find($id),
        ]);
    }

    public function delete($id = null)
    {
        $product = $this->model->find($id);

        if ($product === null) {
            return $this->failNotFound(
                "Product with ID {$id} was not found."
            );
        }

        if (! $this->model->delete($id)) {
            return $this->fail(
                'Unable to delete product.',
                500
            );
        }

        return $this->respondDeleted([
            'status'  => 'success',
            'message' => 'Product deleted successfully.',
            'data'    => [
                'id' => (int) $id,
            ],
        ]);
    }
}

#ส่วนที่ 9: ทดสอบ API แบบครบ CRUD

#GET Products

curl http://localhost:8080/api/v1/products

#GET Product

curl http://localhost:8080/api/v1/products/1

#POST Product

curl -X POST http://localhost:8080/api/v1/products \
  -H "Content-Type: application/json" \
  -d '{
    "name": "USB-C Hub",
    "description": "7-in-1 USB-C Hub",
    "price": 1290,
    "stock": 30
  }'

#PUT Product

curl -X PUT http://localhost:8080/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{
    "name": "USB-C Hub Pro",
    "description": "9-in-1 USB-C Hub",
    "price": 1690,
    "stock": 25
  }'

#PATCH Product

curl -X PATCH http://localhost:8080/api/v1/products/1 \
  -H "Content-Type: application/json" \
  -d '{
    "stock": 50
  }'

#DELETE Product

curl -X DELETE http://localhost:8080/api/v1/products/1

#ส่วนที่ 10: HTTP Status Code ที่ควรใช้

REST API ที่ดีไม่ควรส่ง 200 ทุกกรณี

ควรใช้ HTTP Status ให้ตรงกับเหตุการณ์

Status ความหมาย ตัวอย่าง
200 OK Request สำเร็จ GET, UPDATE, DELETE
201 Created สร้างข้อมูลสำเร็จ POST
204 No Content สำเร็จแต่ไม่มี body DELETE บางรูปแบบ
400 Bad Request Request/Validation ผิด field ไม่ครบ
401 Unauthorized ยังไม่ Authenticate Token ไม่มี/ผิด
403 Forbidden ไม่มีสิทธิ์ role ไม่อนุญาต
404 Not Found ไม่พบ Resource product ไม่มี
409 Conflict ข้อมูลขัดแย้ง unique field ซ้ำ
422 Unprocessable Content semantic validation error ใน API บางรูปแบบ validation
500 Internal Server Error server error exception/database failure

CodeIgniter มี Response Trait ช่วยจัดการ status code หลายกรณี ทำให้ Controller อ่านง่ายขึ้น


#ส่วนที่ 11: เพิ่ม Pagination

ถ้ามีข้อมูลหลักหมื่นรายการ ไม่ควรใช้

$this->model->findAll();

แล้วส่งทั้งหมดกลับไป

ควรใช้ Pagination

ตัวอย่าง

public function index()
{
    $perPage = (int) ($this->request->getGet('per_page') ?? 10);

    $perPage = max(1, min($perPage, 100));

    $products = $this->model
        ->orderBy('id', 'DESC')
        ->paginate($perPage);

    return $this->respond([
        'status' => 'success',
        'data'   => $products,
        'meta'   => [
            'current_page' => $this->model->pager->getCurrentPage(),
            'per_page'     => $perPage,
            'total'        => $this->model->pager->getTotal(),
            'page_count'   => $this->model->pager->getPageCount(),
        ],
    ]);
}

เรียก

GET /api/v1/products?page=2&per_page=20

ควรจำกัดค่า per_page สูงสุดเพื่อป้องกัน client ขอข้อมูลจำนวนมากเกินไป


#ส่วนที่ 12: เพิ่ม Search

สามารถเพิ่ม Query Parameter

GET /api/v1/products?search=keyboard

ตัวอย่าง

public function index()
{
    $search = trim(
        (string) $this->request->getGet('search')
    );

    $builder = $this->model
        ->orderBy('id', 'DESC');

    if ($search !== '') {
        $builder->groupStart()
            ->like('name', $search)
            ->orLike('description', $search)
            ->groupEnd();
    }

    $products = $builder->findAll();

    return $this->respond([
        'status' => 'success',
        'data'   => $products,
    ]);
}

สำหรับระบบจริงมักนำ Search, Filter, Sort และ Pagination มาทำงานร่วมกัน


#ส่วนที่ 13: API Versioning

แทนที่จะใช้

/api/products

แนะนำ

/api/v1/products

เมื่ออนาคตเปลี่ยน contract ครั้งใหญ่ สามารถเพิ่ม

/api/v2/products

โดยไม่ทำให้ Client รุ่นเก่าหยุดทำงานทันที

ตัวอย่าง

$routes->group('api/v1', static function ($routes) {
    $routes->resource('products', [
        'controller' => 'Api\V1\ProductsController',
        'except'     => ['new', 'edit'],
    ]);
});

$routes->group('api/v2', static function ($routes) {
    $routes->resource('products', [
        'controller' => 'Api\V2\ProductsController',
        'except'     => ['new', 'edit'],
    ]);
});

#ส่วนที่ 14: Authentication

ตัวอย่างในบทความนี้ยังเป็น Public API

ระบบจริงควรเพิ่ม Authentication เช่น

Bearer Token
JWT
OAuth 2.0 / OpenID Connect
CodeIgniter Shield
API Key

รูปแบบ Header ที่พบบ่อย

Authorization: Bearer <access_token>

และควรใช้ Route Filter เพื่อป้องกัน endpoint ที่ต้อง Login

แนวคิด

Client
  |
  | Authorization: Bearer ...
  v
Authentication Filter
  |
  +---- invalid ---> 401 Unauthorized
  |
  v
ProductsController
  |
  v
ProductModel
  |
  v
Database

สำหรับ CodeIgniter 4 ควรพิจารณา CodeIgniter Shield หากต้องการระบบ Authentication/Authorization ที่อยู่ใน ecosystem ของ CodeIgniter


#ส่วนที่ 15: CORS

ถ้า Frontend เช่น React, Vue หรือ Angular อยู่คนละ origin กับ API อาจต้องตั้งค่า CORS

ตัวอย่างสถานการณ์

Frontend
http://localhost:5173

API
http://localhost:8080

Browser จะใช้ CORS policy ควบคุม request ข้าม origin

สำหรับ Production ควรกำหนดเฉพาะ origin ที่อนุญาต เช่น

https://app.example.com

ไม่ควรเปิด

Access-Control-Allow-Origin: *

แบบไม่จำเป็น โดยเฉพาะ API ที่เกี่ยวข้องกับ credential หรือข้อมูลสำคัญ


#ส่วนที่ 16: CSRF และ API

CSRF มีความสำคัญโดยเฉพาะ application ที่ใช้ cookie/session authentication

ถ้าเปิด CSRF Filter แบบ global แล้ว API ถูกเรียกจาก client ภายนอก อาจต้องออกแบบ Filter และ Authentication ให้เหมาะสม

ไม่ควรแก้ปัญหาด้วยการปิด security ทั้งหมดโดยไม่วิเคราะห์ threat model

แนวทางขึ้นอยู่กับรูปแบบ Authentication เช่น

Cookie + Session
Bearer Token
OAuth 2.0

แต่ละแบบมีความเสี่ยงและการตั้งค่าไม่เหมือนกัน


#ส่วนที่ 17: รูปแบบ Response ที่สม่ำเสมอ

ระบบใหญ่ควรออกแบบ Response Contract ให้เหมือนกัน

ตัวอย่างสำเร็จ

{
  "status": "success",
  "message": "Product created successfully.",
  "data": {
    "id": 1,
    "name": "Mechanical Keyboard"
  }
}

ตัวอย่างไม่สำเร็จ

{
  "status": 400,
  "error": 400,
  "messages": {
    "name": "The name field is required."
  }
}

ข้อดีคือ Frontend เขียน logic ประมวลผลได้ง่ายกว่า API ที่แต่ละ endpoint ส่งรูปแบบไม่เหมือนกัน


#ส่วนที่ 18: โครงสร้างโปรเจกต์ที่แนะนำ

เมื่อ API เริ่มใหญ่ขึ้น อาจจัดโครงสร้าง

app/
├── Config/
│   └── Routes.php
├── Controllers/
│   └── Api/
│       └── V1/
│           ├── ProductsController.php
│           ├── UsersController.php
│           └── OrdersController.php
├── Database/
│   ├── Migrations/
│   └── Seeds/
├── Entities/
├── Filters/
├── Models/
│   ├── ProductModel.php
│   ├── UserModel.php
│   └── OrderModel.php
├── Services/
└── Validation/

Controller ไม่ควรมี Business Logic จำนวนมาก

แนวคิดที่ดีกว่าเมื่อระบบซับซ้อนคือ

Controller
    |
    v
Service
    |
    v
Model / Repository
    |
    v
Database

ตัวอย่าง

ProductsController
        |
        v
ProductService
        |
        +--> ProductModel
        |
        +--> InventoryService
        |
        +--> AuditLogService

ช่วยให้

  • ทดสอบง่ายขึ้น
  • ลด Controller ที่ยาวเกินไป
  • reuse business logic ได้
  • ลด coupling ระหว่าง HTTP layer กับ domain logic

#ส่วนที่ 19: Security Checklist

ก่อนนำ API ขึ้น Production ควรตรวจอย่างน้อย

  • [ ] ใช้ HTTPS
  • [ ] ปิด development mode
  • [ ] ไม่ commit .env
  • [ ] Validate input ทุก endpoint
  • [ ] ใช้เฉพาะ validated data
  • [ ] จำกัด $allowedFields
  • [ ] ทำ Authentication
  • [ ] ทำ Authorization
  • [ ] จำกัด CORS
  • [ ] Rate Limit endpoint สำคัญ
  • [ ] ไม่ส่ง stack trace ให้ client
  • [ ] จัดการ Database Credential ผ่าน environment
  • [ ] Log error โดยไม่บันทึก password/token
  • [ ] ทำ Pagination กับ endpoint ที่อาจมีข้อมูลจำนวนมาก
  • [ ] ตรวจสอบ File Upload อย่างเข้มงวด หากมี
  • [ ] อัปเดต CodeIgniter และ dependency อย่างสม่ำเสมอ

#ส่วนที่ 20: การ Deploy

ในการพัฒนาเราใช้

php spark serve

แต่ ไม่ควรใช้ Development Server เป็น Production Web Server

Production ควรใช้ Web Server เช่น

Nginx
Apache
Caddy

และต้องตั้ง Document Root ไปยัง

public/

ไม่ใช่ root directory ของ CodeIgniter ทั้งโปรเจกต์

โครงสร้างแนวคิด

Internet
   |
 HTTPS
   |
   v
Nginx / Caddy / Apache
   |
   v
public/index.php
   |
   v
CodeIgniter 4
   |
   v
MySQL

ถ้าใช้ Docker อาจแยก service เป็น

Reverse Proxy
    |
    v
PHP / CodeIgniter
    |
    +---- MySQL
    |
    +---- Redis

#ส่วนที่ 21: สรุป

CodeIgniter 4 มีองค์ประกอบสำหรับสร้าง REST API อยู่ใน Framework โดยตรง ทำให้เราสร้าง CRUD API ได้โดยไม่ต้องติดตั้ง library จำนวนมาก

Flow หลักของตัวอย่างนี้คือ

Client
  |
  | HTTP + JSON
  v
Routes
  |
  v
ResourceController
  |
  +--> Validation
  |
  v
Model
  |
  v
MySQL
  |
  v
JSON Response

ขั้นตอนที่ทำในบทความประกอบด้วย

1. สร้าง CodeIgniter 4 Project
2. ตั้งค่า .env
3. เชื่อมต่อ MySQL
4. สร้าง Migration
5. สร้าง ProductModel
6. สร้าง Resource Routes
7. สร้าง ResourceController
8. ทำ CRUD API
9. Validate JSON Input
10. ใช้ HTTP Status Code
11. ทดสอบด้วย cURL
12. เตรียมแนวทางสำหรับ Authentication, Pagination และ Production

สำหรับผู้ที่เริ่มสร้าง Backend ด้วย PHP จุดเด่นของ CodeIgniter 4 คือมีโครงสร้างไม่ซับซ้อนมาก แต่ยังรองรับแนวคิดสำคัญของ Modern Web API เช่น RESTful routing, JSON response, validation, migration และ modular application structure

เมื่อ API เริ่มมีขนาดใหญ่ ขั้นตอนต่อไปที่ควรศึกษา ได้แก่

CodeIgniter Shield
Authentication / Authorization
JWT / OAuth 2.0
API Testing
Feature Testing
Rate Limiting
CORS
Docker
Redis Cache
Queue / Background Jobs
OpenAPI / Swagger
CI/CD
Observability

#แหล่งอ้างอิง


#Endpoint สรุป

GET    /api/v1/products
GET    /api/v1/products/{id}
POST   /api/v1/products
PUT    /api/v1/products/{id}
PATCH  /api/v1/products/{id}
DELETE /api/v1/products/{id}

พร้อมนำโครงสร้างนี้ไปต่อยอดเป็น API สำหรับ

User Management
Student Management
Inventory
E-Commerce
Booking
Course Management
Project Management
Mobile Application Backend
React / Vue / Next.js Backend API