Mutación de datos
En el capítulo anterior, implementaste búsqueda y paginación usando Parámetros de Búsqueda en la URL y APIs de Next.js. ¡Continuemos trabajando en la página de Facturas añadiendo la capacidad de crear, actualizar y eliminar facturas!
¿Qué son las Acciones de Servidor?
Las Acciones de Servidor (Server Actions) de React te permiten ejecutar código asíncrono directamente en el servidor. Eliminan la necesidad de crear endpoints API para mutar tus datos. En su lugar, escribes funciones asíncronas que se ejecutan en el servidor y pueden ser invocadas desde tus Componentes de Cliente o Servidor.
La seguridad es una prioridad máxima para las aplicaciones web, ya que pueden ser vulnerables a diversas amenazas. Aquí es donde entran las Acciones de Servidor. Incluyen características como cierres encriptados, verificaciones estrictas de entrada, hashing de mensajes de error, restricciones de host y más, todo trabajando en conjunto para mejorar significativamente la seguridad de tu aplicación.
Uso de formularios con Acciones de Servidor
En React, puedes usar el atributo action
en el elemento <form>
para invocar acciones. La acción recibirá automáticamente el objeto nativo FormData, que contiene los datos capturados.
Por ejemplo:
Una ventaja de invocar una Acción de Servidor dentro de un Componente de Servidor es la mejora progresiva: los formularios funcionan incluso si JavaScript no se ha cargado aún en el cliente, como en conexiones de internet más lentas.
Next.js con Acciones de Servidor
Las Acciones de Servidor también están profundamente integradas con el sistema de caché de Next.js. Cuando se envía un formulario a través de una Acción de Servidor, no solo puedes usar la acción para mutar datos, sino que también puedes revalidar la caché asociada usando APIs como revalidatePath
y revalidateTag
.
¡Veamos cómo funciona todo junto!
Creación de una factura
Estos son los pasos que seguirás para crear una nueva factura:
- Crea un formulario para capturar la entrada del usuario.
- Crea una Acción de Servidor e invócala desde el formulario.
- Dentro de tu Acción de Servidor, extrae los datos del objeto
formData
. - Valida y prepara los datos para ser insertados en tu base de datos.
- Inserta los datos y maneja cualquier error.
- Revalida la caché y redirige al usuario de vuelta a la página de facturas.
1. Crea una nueva ruta y formulario
Para comenzar, dentro de la carpeta /invoices
, añade un nuevo segmento de ruta llamado /create
con un archivo page.tsx
:

Usarás esta ruta para crear nuevas facturas. Dentro de tu archivo page.tsx
, pega el siguiente código y tómate un tiempo para estudiarlo:
Tu página es un Componente de Servidor que obtiene customers
y lo pasa al componente <Form>
. Para ahorrar tiempo, ya hemos creado el componente <Form>
por ti.
Navega al componente <Form>
y verás que el formulario:
- Tiene un elemento
<select>
(desplegable) con una lista de clientes. - Tiene un elemento
<input>
para el monto contype="number"
. - Tiene dos elementos
<input>
para el estado contype="radio"
. - Tiene un botón con
type="submit"
.
En http://localhost:3000/dashboard/invoices/create, deberías ver la siguiente interfaz:

2. Crea una Acción de Servidor
Genial, ahora creemos una Acción de Servidor que se llamará cuando se envíe el formulario.
Navega a tu directorio lib/
y crea un nuevo archivo llamado actions.ts
. En la parte superior de este archivo, añade la directiva use server
de React:
Al añadir 'use server'
, marcas todas las funciones exportadas dentro del archivo como Acciones de Servidor. Estas funciones de servidor pueden luego ser importadas y usadas en componentes de Cliente y Servidor. Cualquier función incluida en este archivo que no se use será eliminada automáticamente del paquete final de la aplicación.
También puedes escribir Acciones de Servidor directamente dentro de Componentes de Servidor añadiendo "use server"
dentro de la acción. Pero para este curso, las mantendremos organizadas en un archivo separado. Recomendamos tener un archivo separado para tus acciones.
En tu archivo actions.ts
, crea una nueva función asíncrona que acepte formData
:
Luego, en tu componente <Form>
, importa createInvoice
desde tu archivo actions.ts
. Añade un atributo action
al elemento <form>
y llama a la acción createInvoice
.
Nota importante: En HTML, pasarías una URL al atributo
action
. Esta URL sería el destino donde se enviarían los datos de tu formulario (generalmente un endpoint API).Sin embargo, en React, el atributo
action
se considera un prop especial, lo que significa que React se basa en él para permitir que se invoquen acciones.Detrás de escenas, las Acciones de Servidor crean un endpoint API
POST
. Por eso no necesitas crear endpoints API manualmente cuando usas Acciones de Servidor.
3. Extrae los datos de formData
De vuelta en tu archivo actions.ts
, necesitarás extraer los valores de formData
. Hay varios métodos que puedes usar. Para este ejemplo, usemos el método .get(name)
.
Consejo: Si trabajas con formularios que tienen muchos campos, puedes considerar usar el método
entries()
conObject.fromEntries()
de JavaScript.
Para verificar que todo está conectado correctamente, prueba el formulario. Después de enviarlo, deberías ver los datos que acabas de ingresar en el formulario registrados en tu terminal (no en el navegador).
Ahora que tus datos están en forma de objeto, será mucho más fácil trabajar con ellos.
4. Valida y prepara los datos
Antes de enviar los datos del formulario a tu base de datos, debes asegurarte de que estén en el formato correcto y con los tipos correctos. Si recuerdas de antes en el curso, tu tabla de facturas espera datos en el siguiente formato:
Hasta ahora, solo tienes customer_id
, amount
y status
del formulario.
Validación y coerción de tipos
Es importante validar que los datos de tu formulario coincidan con los tipos esperados en tu base de datos. Por ejemplo, si añades un console.log
dentro de tu acción:
Notarás que amount
es de tipo string
y no number
. ¡Esto se debe a que los elementos input
con type="number"
en realidad devuelven una cadena, no un número!
Para manejar la validación de tipos, tienes algunas opciones. Aunque puedes validar tipos manualmente, usar una biblioteca de validación de tipos puede ahorrarte tiempo y esfuerzo. Para tu ejemplo, usaremos Zod, una biblioteca de validación con enfoque en TypeScript que puede simplificar esta tarea.
En tu archivo actions.ts
, importa Zod y define un esquema que coincida con la forma de tu objeto de formulario. Este esquema validará los formData
antes de guardarlos en una base de datos.
El campo amount
está configurado específicamente para coercer (cambiar) de una cadena a un número mientras también valida su tipo.
Luego puedes pasar tus rawFormData
a CreateInvoice
para validar los tipos:
Almacenar valores en centavos
Es una buena práctica almacenar valores monetarios en centavos en tu base de datos para eliminar errores de punto flotante en JavaScript y garantizar mayor precisión.
Convirtamos el monto a centavos:
Crear nuevas fechas
Finalmente, creemos una nueva fecha con el formato "AAAA-MM-DD" para la fecha de creación de la factura:
5. Insertar los datos en tu base de datos
Ahora que tienes todos los valores que necesitas para tu base de datos, puedes crear una consulta SQL para insertar la nueva factura en tu base de datos y pasar las variables:
Por ahora, no estamos manejando ningún error. Hablaremos de esto en el próximo capítulo. Por ahora, pasemos al siguiente paso.
6. Revalidar y redireccionar
Next.js tiene una caché del enrutador (router cache) del lado del cliente que almacena los segmentos de ruta en el navegador del usuario durante un tiempo. Junto con el prefetching, esta caché garantiza que los usuarios puedan navegar rápidamente entre rutas mientras se reduce el número de solicitudes al servidor.
Como estás actualizando los datos mostrados en la ruta de facturas, quieres limpiar esta caché y activar una nueva solicitud al servidor. Puedes hacer esto con la función revalidatePath
de Next.js:
Una vez que la base de datos se haya actualizado, la ruta /dashboard/invoices
será revalidada y se obtendrán datos frescos del servidor.
En este punto, también quieres redirigir al usuario de vuelta a la página /dashboard/invoices
. Puedes hacer esto con la función redirect
de Next.js:
¡Felicidades! Acabas de implementar tu primera Acción de Servidor (Server Action). Pruébalo añadiendo una nueva factura, si todo funciona correctamente:
- Deberías ser redirigido a la ruta
/dashboard/invoices
al enviar el formulario. - Deberías ver la nueva factura en la parte superior de la tabla.
Actualizar una factura
El formulario de actualización de factura es similar al de creación de factura, excepto que necesitarás pasar el id
de la factura para actualizar el registro en tu base de datos. Veamos cómo puedes obtener y pasar el id
de la factura.
Estos son los pasos que seguirás para actualizar una factura:
- Crear un nuevo segmento de ruta dinámica con el
id
de la factura. - Leer el
id
de la factura de los parámetros (params) de la página. - Obtener la factura específica de tu base de datos.
- Pre-llenar el formulario con los datos de la factura.
- Actualizar los datos de la factura en tu base de datos.
1. Crear un segmento de ruta dinámica con el id
de la factura
Next.js te permite crear Segmentos de Ruta Dinámicos cuando no conoces el nombre exacto del segmento y quieres crear rutas basadas en datos. Esto podría ser títulos de publicaciones de blog, páginas de productos, etc. Puedes crear segmentos de ruta dinámicos envolviendo el nombre de una carpeta entre corchetes. Por ejemplo, [id]
, [post]
o [slug]
.
En tu carpeta /invoices
, crea una nueva ruta dinámica llamada [id]
, luego una nueva ruta llamada edit
con un archivo page.tsx
. La estructura de tus archivos debería verse así:
![Carpeta Invoices con una carpeta anidada [id], y una carpeta edit dentro de ella](https://h8DxKfmAPhn8O0p3.public.blob.vercel-storage.com/learn/light/edit-invoice-route.png)
En tu componente <Table>
, observa que hay un botón <UpdateInvoice />
que recibe el id
de la factura de los registros de la tabla.
Navega a tu componente <UpdateInvoice />
y actualiza el href
del Link
para aceptar la prop id
. Puedes usar literales de plantilla para enlazar a un segmento de ruta dinámico:
2. Leer el id
de la factura de los parámetros (params) de la página
De vuelta en tu componente <Page>
, pega el siguiente código:
Observa cómo es similar a tu página de /create
factura, excepto que importa un formulario diferente (del archivo edit-form.tsx
). Este formulario debe estar pre-llenado con un defaultValue
para el nombre del cliente, el monto de la factura y el estado. Para pre-llenar los campos del formulario, necesitas obtener la factura específica usando id
.
Además de searchParams
, los componentes de página también aceptan una prop llamada params
que puedes usar para acceder al id
. Actualiza tu componente <Page>
para recibir la prop:
3. Obtener la factura específica
Luego:
- Importa una nueva función llamada
fetchInvoiceById
y pasa elid
como argumento. - Importa
fetchCustomers
para obtener los nombres de los clientes para el menú desplegable.
Puedes usar Promise.all
para obtener tanto la factura como los clientes en paralelo:
Verás un error temporal de TypeScript para la prop invoice
en tu terminal porque invoice
podría ser potencialmente undefined
. No te preocupes por eso ahora, lo resolverás en el próximo capítulo cuando añadas manejo de errores.
¡Genial! Ahora, prueba que todo esté conectado correctamente. Visita http://localhost:3000/dashboard/invoices y haz clic en el ícono del lápiz para editar una factura. Después de la navegación, deberías ver un formulario pre-llenado con los detalles de la factura:

La URL también debería actualizarse con un id
de la siguiente manera: http://localhost:3000/dashboard/invoice/uuid/edit
UUIDs vs. Claves autoincrementales
Usamos UUIDs en lugar de claves autoincrementales (ej. 1, 2, 3, etc.). Esto hace que la URL sea más larga; sin embargo, los UUIDs eliminan el riesgo de colisión de IDs, son globalmente únicos y reducen el riesgo de ataques de enumeración, lo que los hace ideales para bases de datos grandes.
Sin embargo, si prefieres URLs más limpias, podrías preferir usar claves autoincrementales.
4. Pasar el id
a la Acción de Servidor
Por último, quieres pasar el id
a la Acción de Servidor para que puedas actualizar el registro correcto en tu base de datos. No puedes pasar el id
como argumento así:
En su lugar, puedes pasar el id
a la Acción de Servidor usando bind
de JS. Esto asegurará que cualquier valor pasado a la Acción de Servidor esté codificado.
Nota: Usar un campo de entrada oculto en tu formulario también funciona (ej.
<input type="hidden" name="id" value={invoice.id} />
). Sin embargo, los valores aparecerán como texto completo en el código fuente HTML, lo que no es ideal para datos sensibles.
Luego, en tu archivo actions.ts
, crea una nueva acción, updateInvoice
:
De manera similar a la acción createInvoice
, aquí estás:
- Extrayendo los datos de
formData
. - Validando los tipos con Zod.
- Convirtiendo el monto a centavos.
- Pasando las variables a tu consulta SQL.
- Llamando a
revalidatePath
para limpiar la caché del cliente y hacer una nueva solicitud al servidor. - Llamando a
redirect
para redirigir al usuario a la página de facturas.
Pruébalo editando una factura. Después de enviar el formulario, deberías ser redirigido a la página de facturas y la factura debería estar actualizada.
Eliminar una factura
Para eliminar una factura usando una Acción de Servidor, envuelve el botón de eliminar en un elemento <form>
y pasa el id
a la Acción de Servidor usando bind
:
Dentro de tu archivo actions.ts
, crea una nueva acción llamada deleteInvoice
.
Como esta acción se llama en la ruta /dashboard/invoices
, no necesitas llamar a redirect
. Llamar a revalidatePath
activará una nueva solicitud al servidor y volverá a renderizar la tabla.
Lectura adicional
En este capítulo, aprendiste a usar Acciones de Servidor para mutar datos. También aprendiste a usar la API revalidatePath
para revalidar la caché de Next.js y redirect
para redirigir al usuario a una nueva página.
También puedes leer más sobre seguridad con Acciones de Servidor para aprendizaje adicional.