Olá pessoal! Através desse simples artigo eu vou ensinar como criamos API RESTful básica, mas utilizando o nosso bom e velho PHP e a ajuda do maravilhoso framework CodeIgniter 4.
Então inspirado na Chuck Norris API, no péssimo humorista brasileiro Leo Lins, a fim de praticar e na completa zueira… 🤣
Resolvi criar uma API com piadas e humor negro brasileiro que está disponibilizada neste repositório no GitHub e pode ser utilizada através deste link:
Sendo assim, vou explicar de forma simples e tentar ser o mais prático possível, como foi feito o básico deste esse projeto.
O que vamos construir?
- API RESTful com 5 endpoints que funcionam de verdade
- Arquitetura MVC bem organizada (sem gambiarras!)
- Base de dados JSON com mais de 1000 piadas
- Validações e tratamento de erros
- Código limpo que você vai conseguir manter
O que você precisa saber antes
- PHP básico (orientação a objetos ajuda)
- Conceitos de API REST (GET, POST, JSON...)
- CodeIgniter 4 (vou explicar tudo, mas é bom ter uma noção)
- Um servidor local (Docker, XAMPP ou WAMP)
Como vamos organizar tudo
Olha, o CodeIgniter 4 já vem com uma estrutura bem definida. Vamos usar só o que precisamos:
darkhumor-api/
├── app/
│ ├── Controllers/
│ │ └── JokesController.php # Aqui fica toda lógica da API
│ ├── Models/
│ │ └── JokesModel.php # Aqui gerenciamos os dados
│ ├── Config/
│ │ ├── Routes.php # Definimos as URLs da API
│ │ └── App.php # Configurações básicas
│ └── Libraries/
│ └── jokes.json # Nossa "base de dados"
├── public/
│ └── index.php # Ponto de entrada da aplicação
└── .htaccess # Para URLs bonitas
Por que essa estrutura?
- Controllers: Recebem as requisições HTTP e devolvem respostas
- Models: Fazem toda a manipulação dos dados
- Config: Configurações da aplicação
- Libraries: Arquivos auxiliares (nosso JSON fica aqui)
Planejamento da API
Ela vai possuir ter 5 endpoints principais:
Método | Endpoint | O que faz |
---|---|---|
GET |
/jokes/random |
Pega uma piada aleatória |
GET |
/jokes/random?category=obesidade |
Piada aleatória de uma categoria |
GET |
/jokes/categories |
Lista todas as categorias |
GET |
/jokes/search?query=gordo |
Busca piadas por palavra |
GET |
/jokes/42 |
Pega uma piada específica pelo ID |
Como vai ser a resposta?
Todas as piadas vão retornar nesse formato JSON:
{
"id": 1043,
"url": "http://localhost/jokes/1043",
"value": "Texto da piada aqui...",
"theme": "categoria da piada"
}
Por que esse formato?
- id: Para identificar unicamente cada piada
- url: Para acessar diretamente a piada
- value: O texto da piada em si
- theme: A categoria/tema da piada
1- Configuração Básica
Antes de mais nada, precisamos configurar o CodeIgniter. Abra o arquivo app/Config/App.php
e ajuste essas configurações:
<?php
// app/Config/App.php
namespace Config;
use CodeIgniter\Config\BaseConfig;
class App extends BaseConfig
{
// Sua URL local (ajuste conforme seu ambiente)
public string $baseURL = 'http://localhost/';
// Remove o index.php das URLs (deixa mais bonito)
public string $indexPage = '';
// Protocolo de URI
public string $uriProtocol = 'REQUEST_URI';
// Idioma padrão
public string $defaultLocale = 'pt-BR';
// Não negociar idioma automaticamente
public bool $negotiateLocale = false;
// Idiomas suportados
public array $supportedLocales = ['pt-BR'];
}
Por que essas configurações?
- $baseURL: É a URL base da sua aplicação.
-
$indexPage = '': Remove o
index.php
das URLs. Ao invés de/index.php/jokes/random
, fica só/jokes/random
- $defaultLocale: Define português brasileiro como padrão
2 - Configurando as Rotas
Agora vamos definir as URLs da nossa API. Abra o arquivo app/Config/Routes.php
:
<?php
// app/Config/Routes.php
use CodeIgniter\Router\RouteCollection;
/**
* @var RouteCollection $routes
*/
// Rota principal (opcional, para uma página inicial)
$routes->get('/', 'JokesController::index');
// Agrupamos todas as rotas de piadas com o prefixo 'jokes'
$routes->group('jokes', function($routes) {
$routes->get('random', 'JokesController::random'); // /jokes/random
$routes->get('categories', 'JokesController::categories'); // /jokes/categories
$routes->get('search', 'JokesController::search'); // /jokes/search
$routes->get('(:num)', 'JokesController::show/$1'); // /jokes/42
});
Vamos entender cada linha:
-
$routes->get('/', 'JokesController::index')
:- Quando alguém acessar a raiz do site, chama o método
index
doJokesController
- Quando alguém acessar a raiz do site, chama o método
-
$routes->group('jokes', function($routes) { ... })
:- Agrupa todas as rotas com o prefixo
jokes
- Assim não precisamos repetir
/jokes
em cada rota
- Agrupa todas as rotas com o prefixo
-
$routes->get('random', 'JokesController::random')
:- URL:
/jokes/random
- Chama o método
random
do controller
- URL:
-
$routes->get('(:num)', 'JokesController::show/$1')
:-
(:num)
captura apenas números -
$1
passa esse número como parâmetro para o métodoshow
- Exemplo:
/jokes/42
→ chamashow(42)
-
3 - Criando o Model
O Model é onde fica toda a lógica de manipulação dos dados. É ele que vai ler o JSON, buscar piadas, filtrar por categoria, etc.
Crie o arquivo app/Models/JokesModel.php
:
<?php
// app/Models/JokesModel.php
namespace App\Models;
use CodeIgniter\Model;
class JokesModel extends Model
{
// Aqui vamos guardar todas as piadas carregadas do JSON
protected $jokesData;
// E aqui as categorias extraídas
protected $categories;
public function __construct()
{
parent::__construct();
// Assim que o model é criado, já carrega os dados
$this->loadJokesData();
}
/**
* Carrega os dados das piadas do arquivo JSON
* Essa função roda uma vez quando o model é instanciado
*/
private function loadJokesData()
{
// APPPATH é uma constante do CodeIgniter que aponta para a pasta app/
$jsonPath = APPPATH . 'Libraries/jokes.json';
// Sempre verificar se o arquivo existe antes de tentar ler
if (!file_exists($jsonPath)) {
throw new \Exception('Arquivo de piadas não encontrado em: ' . $jsonPath);
}
// Lê todo o conteúdo do arquivo JSON
$jsonContent = file_get_contents($jsonPath);
// Converte o JSON em array PHP
$this->jokesData = json_decode($jsonContent, true);
// Verifica se deu erro na conversão do JSON
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Erro ao decodificar JSON: ' . json_last_error_msg());
}
// Extrai as categorias únicas das piadas
$this->categories = $this->extractCategories();
}
/**
* Pega uma piada aleatória
* Método mais simples da nossa API
*/
public function getRandomJoke()
{
$jokes = $this->jokesData['jokes'];
// array_rand() pega um índice aleatório do array
// Depois usamos esse índice para pegar a piada
return $jokes[array_rand($jokes)];
}
/**
* Busca uma piada específica pelo ID
*/
public function getJokeById($id)
{
$jokes = $this->jokesData['jokes'];
// Percorre todas as piadas procurando pelo ID
foreach ($jokes as $joke) {
if ($joke['id'] == $id) {
return $joke; // Achou! Retorna a piada
}
}
// Não achou nada? Retorna null
return null;
}
/**
* Pega piadas de uma categoria específica
* Exemplo: getJokesByCategory('obesidade')
*/
public function getJokesByCategory($category)
{
$jokes = $this->jokesData['jokes'];
$categoryJokes = [];
foreach ($jokes as $joke) {
// stripos() faz busca case-insensitive
// Procura se a categoria está no tema da piada
if (stripos($joke['theme'], $category) !== false) {
$categoryJokes[] = $joke;
}
}
return $categoryJokes;
}
/**
* Busca piadas por qualquer texto
* Procura tanto no texto da piada quanto no tema
*/
public function searchJokes($query)
{
$jokes = $this->jokesData['jokes'];
$results = [];
foreach ($jokes as $joke) {
// Busca no texto da piada OU no tema
$foundInJoke = stripos($joke['joke'], $query) !== false;
$foundInTheme = stripos($joke['theme'], $query) !== false;
if ($foundInJoke || $foundInTheme) {
$results[] = $joke;
}
}
return $results;
}
/**
* Retorna todas as categorias disponíveis
*/
public function getCategories()
{
return $this->categories;
}
/**
* Extrai categorias únicas dos temas das piadas
* Esse método é mais complexo porque alguns temas são compostos
* Exemplo: "Obesidade e AIDS" vira duas categorias: "obesidade" e "aids"
*/
private function extractCategories()
{
// array_column pega só a coluna 'theme' de todas as piadas
$themes = array_column($this->jokesData['jokes'], 'theme');
$categories = [];
foreach ($themes as $theme) {
// Alguns temas são compostos: "Obesidade e AIDS", "Deficiência e regionalismo"
// Vamos separar por "e" ou vírgula
$parts = preg_split('/\s+e\s+|,\s*/', $theme);
foreach ($parts as $part) {
// Limpa espaços e converte para minúsculo
$category = trim(strtolower($part));
// Só adiciona se não estiver vazio e não existir ainda
if (!empty($category) && !in_array($category, $categories)) {
$categories[] = $category;
}
}
}
// Ordena alfabeticamente para ficar organizado
sort($categories);
return $categories;
}
/**
* Método auxiliar para pegar estatísticas (opcional)
*/
public function getStats()
{
return [
'total_jokes' => count($this->jokesData['jokes']),
'total_categories' => count($this->categories),
'description' => $this->jokesData['description'] ?? 'API de Humor Negro'
];
}
}
Por que fazer assim?
-
Carregamento único: Os dados são carregados uma vez no
__construct()
e ficam na memória - Tratamento de erros: Sempre verificamos se o arquivo existe e se o JSON é válido
-
Busca inteligente: Usamos
stripos()
para busca case-insensitive - Categorias dinâmicas: Extraímos automaticamente as categorias dos temas
4 - Criando o Controller - O Maestro da API
Agora vem a parte mais importante: o Controller! É ele que recebe as requisições HTTP, chama o Model e retorna as respostas em JSON.
Crie o arquivo app/Controllers/JokesController.php
:
<?php
// app/Controllers/JokesController.php
namespace App\Controllers;
use CodeIgniter\RESTful\ResourceController;
use CodeIgniter\API\ResponseTrait;
use App\Models\JokesModel;
class JokesController extends ResourceController
{
// Esse trait nos dá métodos prontos para responder JSON
use ResponseTrait;
protected $jokesModel;
public function __construct()
{
// Instancia o model assim que o controller é criado
$this->jokesModel = new JokesModel();
}
/**
* Página inicial (opcional)
* Você pode criar uma view aqui ou só retornar uma mensagem
*/
public function index()
{
return $this->respond([
'message' => 'Bem-vindo à Dark Humor API!',
'endpoints' => [
'GET /jokes/random' => 'Piada aleatória',
'GET /jokes/categories' => 'Lista de categorias',
'GET /jokes/search?query=termo' => 'Busca piadas',
'GET /jokes/{id}' => 'Piada específica'
]
]);
}
/**
* GET /jokes/random
* GET /jokes/random?category=obesidade
*/
public function random()
{
// Verifica se foi passada uma categoria
$category = service('request')->getGet('category');
if ($category) {
// Se tem categoria, chama método específico
return $this->getRandomByCategory($category);
}
// Senão, pega uma piada aleatória qualquer
$randomJoke = $this->jokesModel->getRandomJoke();
$response = $this->formatJokeResponse($randomJoke);
return $this->respond($response);
}
/**
* Método privado para pegar piada aleatória por categoria
*/
private function getRandomByCategory($category)
{
// Busca todas as piadas da categoria
$categoryJokes = $this->jokesModel->getJokesByCategory($category);
// Se não achou nenhuma, retorna erro 404
if (empty($categoryJokes)) {
return $this->failNotFound('Categoria não encontrada: ' . $category);
}
// Pega uma aleatória das que achou
$randomJoke = $categoryJokes[array_rand($categoryJokes)];
$response = $this->formatJokeResponse($randomJoke);
return $this->respond($response);
}
/**
* GET /jokes/categories
* Retorna todas as categorias disponíveis
*/
public function categories()
{
$categories = $this->jokesModel->getCategories();
return $this->respond($categories);
}
/**
* GET /jokes/search?query=gordo
* Busca piadas por texto
*/
public function search()
{
// Pega o parâmetro 'query' da URL
$query = service('request')->getGet('query');
// Se não passou o parâmetro, retorna erro
if (!$query) {
return $this->fail('Parâmetro query é obrigatório', 400);
}
// Busca as piadas
$jokes = $this->jokesModel->searchJokes($query);
$results = [];
// Formata cada piada encontrada
foreach ($jokes as $joke) {
$results[] = $this->formatJokeResponse($joke);
}
// Retorna com o total de resultados
$response = [
'total' => count($results),
'result' => $results
];
return $this->respond($response);
}
/**
* GET /jokes/42
* Retorna uma piada específica pelo ID
*/
public function show($id = null)
{
// Verifica se foi passado um ID
if (!$id) {
return $this->fail('ID é obrigatório', 400);
}
// Busca a piada no model
$joke = $this->jokesModel->getJokeById($id);
// Se não achou, retorna 404
if (!$joke) {
return $this->failNotFound('Piada não encontrada com ID: ' . $id);
}
// Formata e retorna
$response = $this->formatJokeResponse($joke);
return $this->respond($response);
}
/**
* Método auxiliar para formatar a resposta das piadas
* Padroniza o formato de saída da API
*/
private function formatJokeResponse($joke)
{
return [
'id' => $joke['id'],
'url' => base_url('jokes/' . $joke['id']),
'value' => $joke['joke'],
'theme' => $joke['theme']
];
}
}
Vamos entender o que está acontecendo:
1. Herança e Traits
class JokesController extends ResourceController
{
use ResponseTrait;
- ResourceController: Classe base do CodeIgniter para APIs REST
-
ResponseTrait: Nos dá métodos como
respond()
,fail()
,failNotFound()
2. Método random()
$category = service('request')->getGet('category');
-
service('request')
pega a instância da requisição HTTP -
getGet('category')
busca o parâmetrocategory
na URL - Se
/jokes/random?category=obesidade
,$category
será"obesidade"
3. Tratamento de Erros
if (empty($categoryJokes)) {
return $this->failNotFound('Categoria não encontrada');
}
- Sempre validamos se encontramos dados
-
failNotFound()
retorna HTTP 404 automaticamente -
fail()
retorna HTTP 400 (Bad Request)
4. Formatação Consistente
private function formatJokeResponse($joke)
- Método privado que padroniza todas as respostas
- Evita repetir código
- Se precisar mudar o formato, muda só aqui
5 - Estrutura do JSON
Para este projeto, vamos usar um arquivo JSON como base de dados. É simples, rápido e funciona perfeitamente para uma API de piadas.
Crie o arquivo app/Libraries/jokes.json
e a estrutura do json será semelhante a essa:
{
"total_jokes": 1000,
"description": "Coleção de piadas de humor negro em português",
"jokes": [
{
"id": 1,
"joke": "Sou gordo! Adoro comer e não gosto de fazer exercício. Como vou emagrecer? Pegando AIDS!",
"theme": "Obesidade e AIDS"
},
{
"id": 2,
"joke": "Tem ser humano que não é 100% humano. O nordestino no avião? 72%.",
"theme": "Preconceito regional"
},
{
"id": 3,
"joke": "Se você for no zoológico, os animais vão tirar fotos de você.",
"theme": "Obesidade"
}
// ... continue adicionando suas piadas aqui
]
}
Por que essa estrutura?
- total_jokes: Facilita estatísticas sem contar o array toda vez
- description: Metadados sobre a coleção
- jokes: Array principal com todas as piadas
- id: Identificador único e sequencial (1, 2, 3...)
- joke: O texto da piada em si
- theme: Categoria para filtros e buscas
O arquivo .json completo com todas as piadas está disponibilizado neste repositório do GitHub.
Testando Nossa API
Agora que tudo está pronto, vamos testar se nossa API está funcionando! Vou te mostrar várias formas de testar.
Crie um arquivo test.php
para testar:
<?php
// Função auxiliar para testar endpoints
function testEndpoint($url) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true);
}
// Testa piada aleatória
$randomJoke = testEndpoint('http://localhost/jokes/random');
echo "Piada aleatória: " . $randomJoke['value'] . "\n";
// Testa categorias
$categories = testEndpoint('http://localhost/jokes/categories');
echo "Total de categorias: " . count($categories) . "\n";
// Testa busca
$searchResults = testEndpoint('http://localhost/jokes/search?query=gordo');
echo "Piadas encontradas: " . $searchResults['total'] . "\n";
?>
Respostas Esperadas
Piada aleatória:
{
"id": 1043,
"url": "http://localhost/jokes/1043",
"value": "Eu sou tão feio que meu espelho me deu um processo...",
"theme": "Aparência"
}
Busca por categoria:
{
"id": 23,
"url": "http://localhost/jokes/23",
"value": "Gordo no ônibus: parece um dinossauro entrando.",
"theme": "Obesidade"
}
Lista de categorias:
[
"aborto",
"abuso",
"acidente",
"amizade",
"animais",
"aparência"
]
Busca por texto:
{
"total": 74,
"result": [
{
"id": 1,
"url": "http://localhost/jokes/1",
"value": "Sou gordo! Adoro comer...",
"theme": "Obesidade"
}
]
}
Bom, é isso ai! Se você leu até aqui, espero que tenha te ajudado!
Criamos uma API RESTful completamente funcional utilizando as facilidades que CodeIgniter proporciona com sua estrutura MVC, criando uma ótima base para podermos explorar melhorias nas próximas implementações com recursos mais avançados, como:
- Cache: Implementar cache Redis para performance
- Paginação: Adicionar paginação na busca
- Rate Limiting: Limitar requisições por IP
- Logs: Implementar logs estruturados
- Validação: Validar parâmetros de entrada
- Autenticação JWT: Para APIs privadas
- Upload de piadas: Endpoint para adicionar piadas
- Favoritos: Sistema de piadas favoritas
- Estatísticas: Analytics de uso da API
- Webhooks: Notificações de novas piadas
Existe um projeto 100% open-source desta API e está disponibilizada neste repositório no GitHub e para utilização através deste link: https://darkhumor-api.ddns.net/
Links Recomendados
- CodeIgniter 4 Docs - Documentação oficial
- REST API Best Practices - Boas práticas
- JSON Validator - Validar seu JSON
- Postman - Testar APIs
Tags: #PHP
#CodeIgniter4
#API
#REST
#JSON
#WebDevelopment
#Backend
#MVC
Top comments (0)