Curso de Laravel
CRUD en Laravel: crear registros desde un formulario
Por Víctor Peña · Publicado el
Hola, ¿cómo están? Continuando con el CRUD, hoy veremos la C de crear: el formulario y el guardado.
¡Empecemos!
Los dos métodos
El CRUD separa esto en dos pasos, y entender por qué evita confusiones:
create()responde a GET y muestra el formulario.store()responde a POST y guarda los datos.
Son dos peticiones distintas a la misma dirección con métodos diferentes. La primera no modifica nada; la segunda sí.
El método create
public function create()
{
return view('pacientes.create');
}
Cuando el formulario necesita datos —para llenar un desplegable, por ejemplo— se pasan aquí:
public function create()
{
$doctores = Doctor::where('activo', true)
->orderBy('apellido')
->pluck('nombre', 'id');
return view('citas.create', compact('doctores'));
}
Ese pluck('nombre', 'id') devuelve directamente el par clave-valor que necesita un <select>.
El formulario
@extends('layouts.app')
@section('titulo', 'Nuevo paciente')
@section('contenido')
<h1>Registrar paciente</h1>
<form method="POST" action="{{ route('pacientes.store') }}">
@csrf
<x-campo nombre="cedula" etiqueta="Cédula" requerido />
<x-campo nombre="nombre" etiqueta="Nombre" requerido />
<x-campo nombre="apellido" etiqueta="Apellido" requerido />
<x-campo nombre="fecha_nacimiento" etiqueta="Fecha de nacimiento"
tipo="date" requerido />
<x-campo nombre="telefono" etiqueta="Teléfono" />
<x-campo nombre="correo" etiqueta="Correo" tipo="email" />
<div class="acciones">
<button type="submit">Guardar</button>
<a href="{{ route('pacientes.index') }}">Cancelar</a>
</div>
</form>
@endsection
Aquí se ve el valor del componente de campo: seis campos en seis líneas, con etiqueta, old() y mensaje de error resueltos dentro.
@csrf es obligatorio. Sin él, Laravel responde con un error 419.
El método store
public function store(GuardarPacienteRequest $request)
{
$paciente = Paciente::create($request->validated());
return redirect()
->route('pacientes.show', $paciente)
->with('exito', 'Paciente registrado correctamente');
}
Tres líneas, y hace todo lo necesario: valida, guarda y redirige con un mensaje.
La validación ocurre antes de entrar al método, gracias al Form Request. Si llegas aquí, los datos son correctos.
POST-Redirect-GET
Fíjate en que el método redirige en lugar de devolver una vista. No es un detalle estético.
Si devolvieras la vista directamente, el navegador se quedaría en una URL que respondió a un POST. Cuando el usuario recargue, el navegador preguntará si quiere reenviar los datos, y si dice que sí, se crea un registro duplicado.
Es el patrón que vimos en el curso de PHP: tras guardar, siempre redirigir.
Mostrar el mensaje
El with('exito', ...) guarda el mensaje para la siguiente petición. En el layout:
@if (session('exito'))
<div class="alerta-exito">{{ session('exito') }}</div>
@endif
Al estar en el layout, cualquier redirección con mensaje lo muestra automáticamente, sin repetir el bloque en cada vista.
Campos relacionados
Cuando el formulario incluye una clave foránea:
<div class="campo">
<label for="doctor_id">Doctor</label>
<select name="doctor_id" id="doctor_id" required>
<option value="">Selecciona un doctor</option>
@foreach ($doctores as $id => $nombre)
<option value="{{ $id }}" @selected(old('doctor_id') == $id)>
{{ $nombre }}
</option>
@endforeach
</select>
@error('doctor_id')
<span class="mensaje-error">{{ $message }}</span>
@enderror
</div>
La directiva @selected() conserva la opción elegida si la validación falla.
Y la regla de validación correspondiente:
'doctor_id' => ['required', 'exists:doctores,id'],
exists importa. Sin ella, alguien podría modificar el HTML y enviar un id que no existe, o el de un doctor desactivado.
Crear con relaciones
Si el registro pertenece a otro, créalo desde la relación:
public function store(GuardarCitaRequest $request, Paciente $paciente)
{
$cita = $paciente->citas()->create($request->validated());
return redirect()
->route('citas.show', $cita)
->with('exito', 'Cita agendada');
}
Eso asigna el paciente_id automáticamente, sin que puedas equivocarte ni que nadie pueda falsificarlo desde el formulario.
Asignar el usuario actual
Un caso muy frecuente:
public function store(GuardarCitaRequest $request)
{
$cita = Cita::create([
...$request->validated(),
'creado_por' => auth()->id(),
]);
return redirect()->route('citas.index')->with('exito', 'Cita agendada');
}
Nunca aceptes el creado_por desde el formulario. Ese dato lo pone el servidor.
Varias operaciones a la vez
Cuando crear un registro implica más de una escritura, envuélvelo en una transacción:
public function store(GuardarCitaRequest $request)
{
$cita = DB::transaction(function () use ($request) {
$cita = Cita::create($request->validated());
$cita->estudios()->attach($request->estudios ?? []);
$cita->paciente->increment('total_citas');
return $cita;
});
return redirect()->route('citas.show', $cita)->with('exito', 'Cita agendada');
}
Si algo falla a mitad, se revierte todo. Es la idea de atomicidad que vimos en el curso de MySQL.
Guardar y seguir creando
Un detalle que agradecen los usuarios que cargan muchos registros seguidos:
<button type="submit" name="accion" value="guardar">Guardar</button>
<button type="submit" name="accion" value="guardar_y_nuevo">Guardar y crear otro</button>
if ($request->accion === 'guardar_y_nuevo') {
return redirect()
->route('pacientes.create')
->with('exito', 'Paciente registrado. Puedes crear otro.');
}
return redirect()->route('pacientes.show', $paciente);
Parece menor, pero para quien carga cincuenta registros en una sesión cambia bastante el trabajo.
Evitar el envío doble
Un usuario impaciente puede pulsar «Guardar» dos veces. Además de la protección con transacción, conviene desactivar el botón:
<button type="submit" onclick="this.disabled=true; this.form.submit();">
Guardar
</button>
Y para casos donde no puede haber duplicados, una restricción unique en la base de datos es la única garantía real. La validación puede fallar en una carrera entre dos peticiones simultáneas; la base de datos, no.
Errores comunes
- Olvidar
@csrfy recibir un 419. - Devolver una vista en lugar de redirigir tras el POST.
- Usar
$request->all()en lugar devalidated(). - Aceptar la clave foránea del usuario sin
exists. - Poner
creado_poren el formulario en vez de asignarlo en el servidor. - Varias escrituras sin transacción.
Para cerrar
Crear un registro son dos métodos y un formulario, pero hay tres cosas que no se pueden saltar: @csrf, validar con Form Request y redirigir después de guardar.
En la siguiente lección veremos la edición, que reutiliza casi todo lo de hoy.
Saludos y éxitos.
Norvic Software
Desarrollamos el software que tu empresa necesita
Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.
- Chatbots con Inteligencia Artificial
- Creación de agentes de IA
- Integración de IA en tus sistemas
- Desarrollo de software a medida
- Aplicaciones móviles iOS y Android
- Consultoría y asesoramiento técnico
Cotización sin costo · Respuesta directa por WhatsApp