7.1 Creación de APIs REST en Laravel

7.1.0 Rutas API en Laravel 12

En Laravel 12, a diferencia de versiones anteriores, el archivo routes/api.php no viene incluido por defecto. Laravel ahora permite habilitarlo opcionalmente para mantener la aplicación más ligera si no vas a construir una API.

Activar el sistema de rutas API

Para trabajar con rutas API, primero debemos ejecutar el siguiente comando Artisan:

Instalar soporte de rutas API

1
php artisan install:api

Este comando:

  • Crea automáticamente el archivo routes/api.php.
  • Registra el archivo en el sistema de rutas.
  • Aplica el middleware api a las rutas definidas allí.
  • Añade el prefijo /api a todas las rutas de ese archivo.

En el proceso nos pude pedir crear una nueva migración para la tabla api_tokens, podemos decir que sí aunque de momento no la vamos a utilizar ya que no hemos visto la autenticación.

¿Dónde se registra esta configuración?

Laravel configura los archivos de rutas en bootstrap/app.php. Una vez activadas, podrás ver una línea como esta:

Registro de rutas API con prefijo

1
2
3
4
5
->withRouting(
    api: __DIR__.'/../routes/api.php',
    apiPrefix: 'api',
    // ...
)

Si quieres cambiar el prefijo, por ejemplo a api/admin, puedes modificar la clave apiPrefix:

Personalizar prefijo de rutas API

1
2
3
4
->withRouting(
    api: __DIR__.'/../routes/api.php',
    apiPrefix: 'api/admin',
)

A partir de este momento, cualquier ruta definida en routes/api.php responderá a URLs que empiecen por /api/ (o el prefijo que hayas definido).

Importante

El archivo web.php sigue existiendo por defecto y está pensado para rutas que devuelven vistas HTML. Por tanto, recuerda usar api.php exclusivamente para tu API REST.


7.1.1 Introducción

Una API (Application Programming Interface) permite a aplicaciones diferentes comunicarse entre sí, intercambiando datos en formatos como JSON. Las APIs REST usan los verbos HTTP (GET, POST, PUT, DELETE) para definir operaciones sobre recursos.

Laravel ofrece todas las herramientas necesarias para construir APIs modernas, organizadas y seguras. En este tema construiremos paso a paso una API para el recurso Note, que ya conocemos de los temas anteriores.


7.1.2 Definir Rutas de API

7.1.2.1 Ficheros de rutas y su organización

Laravel separa las rutas para aplicación web y API:

Archivo Propósito
routes/web.php Rutas para vistas HTML (interfaz web)
routes/api.php Rutas para responder en JSON (API REST)

Estas rutas están cargadas desde el archivo app/Providers/RouteServiceProvider.php, que se encarga de:

  • Aplicar el prefijo /api automáticamente a las rutas definidas en api.php.
  • Asignar middleware api, que aplica limitaciones como throttling, formato JSON, etc.

Ejemplo del prefijo automático

1
2
3
Route::get('/notes', function () {
    return ['mensaje' => 'Esta es la API de notas'];
});
Accediendo a http://localhost:8000/api/notes, obtendrás: { "mensaje": "Esta es la API de notas" }


7.1.3 Crear Controlador para la API

Antes de generar el controlador asegurarnos que tenemos create la table de notas notes en nuestra base de datos. En caso de no tenerla, podemos crearla con el siguiente comando:

php artisan make:migration create_notes_table
Esto generará un archivo de migración en database/migrations que podemos editar para definir la estructura de la tabla notes. Asegúrate de que la migración tenga el siguiente contenido:

Migración para la tabla notes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateNotesTable extends Migration
{
    public function up()
    {
        Schema::create('notes', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->text('description');
            $table->date('date');
            $table->boolean('done')->default(false);
            $table->timestamps();
        });
    }

    public function down()
    {
        Schema::dropIfExists('notes');
    }
}

Después de editar la migración, ejecuta el siguiente comando para crear la tabla:

php artisan migrate
Esto creará la tabla notes en tu base de datos.

también podemos crear el modelo Note con el siguiente comando:

php artisan make:model Note
Esto generará el modelo Note en app/Models/Note.php.

7.1.3.1 Generar un controlador API

Usamos el flag --api para generar un controlador que sólo incluye los métodos necesarios para una API CRUD:

php artisan make:controller Api/NoteController --api

Esto creará el archivo en app/Http/Controllers/Api/NoteController.php con los métodos: index, store, show, update, destroy.

Controlador NoteController

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Note;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
class NoteController extends Controller
{
    public function index(): JsonResponse
    {
        // Listar todas las notas
    }

    public function show(Note $note): JsonResponse
    {
        // Mostrar una nota por ID
    }

    public function store(Request $request): JsonResponse
    {
        // Crear una nueva nota
    }

    public function update(Request $request, Note $note): JsonResponse
    {
        // Actualizar una nota existente
    }

    public function destroy(Note $note): JsonResponse
    {
        // Eliminar una nota
    }
}

El controlador NoteController extiende de Controller y usa el modelo Note para interactuar con la base de datos.

7.1.3.2 Crear las rutas API para Notes

En routes/api.php:

Ruta resource para Notes

1
2
3
use App\Http\Controllers\Api\NoteController;

Route::apiResource('notes', NoteController::class);

Por seguridadd podemos utilizar only() para definir las rutas que queremos habilitar:

Route::apiResource('notes', NoteController::class)->only([
    'index', 'show', 'store', 'update', 'destroy'
]);
Esto generará automáticamente las rutas necesarias para el controlador NoteController usando el método apiResource. Laravel se encarga de crear las rutas RESTful para los métodos del controlador.

Esto define rutas como:

  • GET /api/notes
  • GET /api/notes/{id}
  • POST /api/notes
  • PUT/PATCH /api/notes/{id}
  • DELETE /api/notes/{id}

Puedes comprobarlas con:

php artisan route:list --path=api/notes

7.1.4 Implementar el CRUD API para Notes

7.1.4.0 Modelo Note

Asegúrate de que el modelo Note está correctamente definido con $fillable:

Modelo Note con fillable

1
2
3
4
class Note extends Model
{
    protected $fillable = ['title', 'description', 'date', 'done'];
}

7.1.4.1 Códigos de Estado HTTP en APIs

En una API REST, es importante devolver códigos de estado HTTP apropiados para indicar si la operación fue exitosa o si ocurrió un error.

A continuación, una tabla con los códigos más comunes y su uso recomendado:

Código Nombre Cuándo se usa
200 OK Éxito La petición fue exitosa (por ejemplo, GET, PUT, DELETE)
201 Created Recurso creado Se ha creado un nuevo recurso correctamente (por ejemplo, POST)
204 No Content Sin contenido La petición fue exitosa pero no se devuelve ningún contenido (opcional tras DELETE)
400 Bad Request Petición incorrecta Cuando el cliente envía datos inválidos
401 Unauthorized No autorizado Cuando el usuario no está autenticado
403 Forbidden Prohibido El usuario está autenticado pero no tiene permisos
404 Not Found No encontrado El recurso solicitado no existe
422 Unprocessable Entity Entidad no procesable Validaciones fallidas en los datos enviados
500 Internal Server Error Error del servidor Error inesperado en el servidor

¿Por qué usamos 200, 201 y 204 en este tema?

  • Usamos 200 OK en respuestas normales donde devolvemos datos (GET, PUT, DELETE).
  • Usamos 201 Created al crear un recurso con POST para indicar que se creó exitosamente.
  • Usaríamos 204 No Content si quisiéramos responder a un DELETE sin mensaje (aunque aquí devolvemos mensaje con 200).

7.1.4.2 Método index() – Listar notas

Listar todas las notas

1
2
3
4
5
6
7
public function index(): JsonResponse
{
    return response()->json([
        'success' => true,
        'data' => Note::all()
    ], 200);
}

7.1.4.3 Método show() – Mostrar una nota

Mostrar una nota por ID

1
2
3
4
5
6
7
public function show(Note $note): JsonResponse
{
    return response()->json([
        'success' => true,
        'data' => $note
    ], 200);
}

7.1.4.4 Método store() – Crear una nota

Guardar nueva nota

1
2
3
4
5
6
7
8
9
public function store(Request $request): JsonResponse
{
    $note = Note::create($request->all());
    return response()->json([
        'success' => true,
        'message' => 'Nota creada correctamente.',
        'data' => $note
    ], 201);
}

7.1.4.5 Método update() – Modificar nota

Actualizar una nota

1
2
3
4
5
6
7
8
9
public function update(Request $request, Note $note): JsonResponse
{
    $note->update($request->all());
    return response()->json([
        'success' => true,
        'message' => 'Nota actualizada correctamente.',
        'data' => $note
    ], 200);
}

7.1.4.6 Método destroy() – Eliminar nota

Eliminar una nota

1
2
3
4
5
6
7
8
public function destroy(Note $note): JsonResponse
{
    $note->delete();
    return response()->json([
        'success' => true,
        'message' => 'Nota eliminada correctamente.'
    ], 200);
}

7.1.5 API Resources

Laravel permite transformar la salida de tus APIs con clases Resource que te dan control sobre el formato.

7.1.5.1 Crear un API Resource

php artisan make:resource NoteResource

Crea el archivo en App\Http\Resources\NoteResource.php

7.1.5.2 Personalizar la transformación

Ejemplo de transformación en NoteResource

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public function toArray($request)
{
    return [
        'id' => $this->id,
        'titulo' => $this->title,
        'descripcion' => $this->description,
        'fecha' => $this->date,
        'estado' => $this->done ? 'Completada' : 'Pendiente'
    ];
}

7.1.5.3 Usar el recurso en el controlador

Devolver colección con NoteResource

1
2
3
4
5
6
use App\Http\Resources\NoteResource;

public function index(): JsonResponse
{
    return response()->json(NoteResource::collection(Note::all()));
}

Pero de esta manera no estamos devolvien ni el estado ni el mensaje de éxito. Para devolverlo, podemos envolver la colección en un array:

1
2
3
4
return response()->json([
    'success' => true,
    'data' => NoteResource::collection(Note::all())
], 200);

De esta manera, la respuesta incluirá el estado y el mensaje de éxito.

En los demás métodos no devolvemos una colección sino un solo elemento. Por ello por ejemñlo para el método show() podemos hacer lo siguiente:

Devolver una sola nota con NoteResource

1
2
3
4
5
6
7
public function show(Note $note): JsonResponse
{
    return response()->json([
        'success' => true,
        'data' => new NoteResource($note)
    ], 200);
}

Ahora sería aplicable a todos los métodos que devuelven un solo elemento, como store()y update(). Al destroy() no puesto que no devuelve los datos de la nota eliminada.


7.1.6 Validación de los datos

Al igual que en los formularios, es importante validar los datos que recibimos en la API. Laravel ofrece un sistema de validación muy potente. Empezaremos por validar los datos en el método store() y update(). Para ello vamos a crear la clase NoteRequest:

php artisan make:request NoteRequest
Esto generará el archivo en app/Http/Requests/NoteRequest.php.

Clase NoteRequest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Contracts\Validation\Validator;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Validation\ValidationException;

class NoteRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true;
    }

    public function rules(): array
    {
        return [
            'title' => 'required|string|max:255',
            'description' => 'required|string',
            'date' => 'required|date',
            'done' => 'boolean'
        ];
    }

}

esta función al igual que en los formularios, define las reglas de validación. En este caso: - title: requerido, cadena de texto, máximo 255 caracteres. - description: requerido, cadena de texto. - date: requerido, debe ser una fecha válida. - done: booleano (opcional).

En caso de error en la validación, Laravel devolverá automáticamente un error 422 con los detalles del error.

Ejemplo de error de validación

1
2
3
4
5
6
7
8
{
    "message": "The given data was invalid.",
    "errors": {
        "title": [
            "The title field is required."
        ]
    }
}

Podemos personalizar la respuesta de error en el método failedValidation() en la clase NoteRequest:

Personalizar la respuesta de error

1
2
3
4
5
6
7
8
protected function failedValidation(Validator $validator)
{
    throw new HttpResponseException(response()->json([
        'success' => false,
        'message' => 'Error de validación',
        'errors' => $validator->errors()
    ], 422), [], JSON_UNESCAPED_UNICODE);
}

Importante

En los parámetros de json() podemos añadir el tercer parámetro JSON_UNESCAPED_UNICODE para evitar que los caracteres especiales se escapen. Esto es útil si estás trabajando con caracteres no ASCII. Sino los acentos y caracteres especiales se escaparán y no se verán correctamente en la respuesta.

7.1.7 Conclusiones

  • Las APIs REST son ideales para aplicaciones SPA, móviles o integraciones.
  • Laravel permite definir rutas específicas para API con prefijos automáticos y middleware personalizado.
  • Los controladores API están enfocados a respuestas JSON.
  • ApiResource permite estructurar y controlar la salida de tus datos.

Si todo ha ido bien aquí tenemos una API REST completa para el recurso Note que podemos probar con herramientas como Postman o RestClient:

Ejemplo de prueba con Postman
###
GET http://localhost:8080/api/notes
###
// Creamos la primer nota
POST http://localhost:8080/api/notes HTTP/1.1
content-type: application/json

{
    "title": "First Note",
    "description": "This is the first note",
    "date": "2023-10-01",
    "done": false
}
###
// creamos la segunda nota
POST http://localhost:8080/api/notes HTTP/1.1
content-type: application/json

{
    "title": "Second Note",
    "description": "This is the fisecondrst note",
    "date": "2023-10-01",
    "done": true
}
###
//Creamos la tercera nota
POST http://localhost:8080/api/notes HTTP/1.1
content-type: application/json

{
    "title": "Third Note",
    "description": "This is the third note",
    "date": "2023-10-01",
    "done": false
}
### Modificamos la tercera nota
PUT http://localhost:8080/api/notes/3 HTTP/1.1
content-type: application/json

{
    "id": 3,
    "title": "Third Note",
    "description": "This is the third note",
    "date": "2023-10-01",
    "done": true
}

### Mostrar la nota con id 3
GET http://localhost:8080/api/notes/3

### Eliminar la nota con id 3
DELETE http://localhost:8080/api/notes/3

7.1.7 Ejercicio: Crear una API para Products

Crea una API REST completa para productos similar a la de notas:

  1. Crea el controlador Api\ProductController con --api.
  2. Define las rutas con apiResource.
  3. Implementa los métodos index, store, show, update, destroy.
  4. Crea una clase ProductResource para personalizar la salida.
  5. Usa Postman o Insomnia para probar tu API.
Solución sugerida
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// routes/api.php
use App\Http\Controllers\Api\ProductController;
Route::apiResource('products', ProductController::class);

// app/Http/Controllers/Api/ProductController.php
public function index(): JsonResponse
{
    return response()->json(ProductResource::collection(Product::all()));
}

public function store(Request $request): JsonResponse
{
    $product = Product::create($request->all());
    return response()->json(new ProductResource($product), 201);
}

public function show(Product $product): JsonResponse
{
    return response()->json(new ProductResource($product));
}

public function update(Request $request, Product $product): JsonResponse
{
    $product->update($request->all());
    return response()->json(new ProductResource($product));
}

public function destroy(Product $product): JsonResponse
{
    $product->delete();
    return response()->json(null, 204);
}

// app/Http/Resources/ProductResource.php
public function toArray($request)
{
    return [
        'id' => $this->id,
        'nombre' => $this->name,
        'precio' => '$' . number_format($this->price, 2),
        'stock' => $this->stock
    ];
}