00:00 / 00:00
13. Server Actions
Como hemos visto con la integración con Content Island, Astro puede interacutar con APIs de servidor.
Pero hay ocasiones en las que igual queremos tener una lógica de servidor encapsulada en nuestra aplicación, e interactuar con ella cuando se ejecuta la página en el navegador.
Aquí tenemos varias opciones:
Podemos tener nuestra API de Backend y en la parte de front, si consumimos desde cliente, configurar CORS o lo que haga falta...
También podemos tene nuestra API De Backend en otro proyecto, pero a la hora del deploy copiamos a la carpeta
dist
opublic
del build de nuestro proyecto Astro, en el despliegue completo y nos aseguramos de que estamos en el mismo dominio.Otra opción es utilizar las Server Actions de Astro.
La Server Actions hacen algo parecido a la opción 2 (un servidor ligero en el mismo dominio), pero de una manera más sencilla.
¡ Ojo ! ¿Esto está bien? ¿No es Sphagetti Code? Es buena aproximacíon cuando la interacción con el server es pequeña, por ejemplo, si vamos a implementar un formulario de contacto y queremos enviar el corrreo desde la parte servidora por motivos de seguridad.
Nos ponemos manos a la obra...
Paso a paso
En este caso vamos a almacenar el número de Likes en el servidor, pero seguimos interactuando con el cliente, es decir, vamos a tener un botón que al pulsarlo incrementa el número de likes y lo muestra en pantalla.
Por simplicidad vamos a almacenar ese valor en una variable en memoria (lo suyo sería guardarlo en base de datos y además guardar un mapeo de like por cada id de lección).
Lo primero que hacemos es definir el setup de la acción de servidor (tocaremos el archivo astro.config), aquí podemos elegir si tirar de Node.js
, Vercel
, Netifly
o Deno
, ... es decir en cuanto elegimos está opción Astro va a dejar de generar un sitio 100% estático, y nos hará falta poder desplegar en un servidor que soporte nodejs o lo que hayamos elegido.
Elegimos node:
npm install @astrojs/node
Y en astro.config.mjs añadimos la configuración:
./astro.config.mjs
import { defineConfig, envField } from "astro/config";
import react from "@astrojs/react";
+ import node from '@astrojs/node';
export default defineConfig({
integrations: [react()],
+ adapter: node({ mode: 'standalone' }), // Configuración para Node.js
env: {
schema: {
CONTENT_ISLAND_SECRET_TOKEN: envField.string({
context: "server",
access: "secret",
optional: false,
default: "INFORM_VALID_TOKEN",
}),
},
},
});
En este ejemplo le indicamos a Astro que vamos a usar el adaptador de Node.js
en modo standalone
.
Esto significa que la aplicación se ejecutará como un servidor independiente, sin depender de otro servidor externo.
Si lo preferimos, también podemos integrar Astro dentro de un servidor ya existente como por ejemplo Express, utilizando la opción middleware
.
Vamos a simular que tenemos un sitio para guardar los datos (aquí lo normal sería una base de datos, por simplicidad, vamos a tirar de variable en memoria)
./src/actions/repository.ts
let likeCount = 0; // Contador en memoria
export const getLikes = async () => {
return likeCount;
};
export const addLike = async () => {
likeCount += 1;
return likeCount;
};
Vamos a definir el modelo de datos que vamos a utilizar para las respuestas de nuestras acciones de servidor:
./src/actions/model.ts
export type LikesResponse = { likes: number };
Y definimos la acción de servidor, vamos a tiparla también, ojo, es muy importante crearlo en la carpeta actions
para que Astro lo reconozca como una acción de servidor.
./src/actions/index.ts
import { defineAction } from "astro:actions";
import { addLike, getLikes } from "./repository";
import type { LikesResponse } from "./model";
export const server = {
addLike: defineAction<LikesResponse>({
async handler() {
return { likes: await addLike() };
},
}),
getLikes: defineAction<LikesResponse>({
async handler() {
return { likes: await getLikes() };
},
}),
};
Aquí tenemos dos acciones de servidor:
addLike
: que incrementa el contador de likes y devuelve el nuevo valorgetLikes
: que devuelve el número actual de likes.
Si te fijas Astro utiliza convención basada en carpetas para definir las acciones de servidor, en este caso hemos creado una carpeta
actions
y dentro de ella hemos definido los archivos que contienen la lógica de negocio y el modelo de datos.
Vamos ahora a cambiar el component de like que creamos en React para que haga uso de estas funciones:
./src/components/LikeButton.component.tsx
import { useState, useEffect } from "react";
+ import { actions } from "astro:actions";
const Like: React.FC = () => {
const [likes, setLikes] = useState<number>(0);
useEffect(() => {
// Cargar los likes desde localStorage al montar el componente
- const storedLikes = localStorage.getItem("likes");
- if (storedLikes) {
- setLikes(parseInt(storedLikes, 10));
- }
+ actions.getLikes().then((response) => {
+ setLikes(response?.data?.likes ?? 0);
+ });
}, []);
+ const handleLike = async () => {
- const handleLike = () => {
- const newLikes = likes + 1;
- setLikes(newLikes);
- localStorage.setItem("likes", newLikes.toString());
+ const result = await actions.addLike();
+ setLikes(result?.data?.likes ?? 0);
};
return (
<button
onClick={handleLike}
style={{
fontSize: "1.5rem",
border: "none",
background: "transparent",
cursor: "pointer",
}}
>
👍 {likes}
</button>
);
};
export default Like;
Si ahora ejecutamos podemos ver como funciona:
- Vemos que avanza el contador de likes.
- Podemos abrir network tab.
- Podemos ejecutar el server en modo JS Debugging y poner directamente los breakpoints de servidor en en VSCode.
- Y los de react en el navegador.
Te ánimo a que ejecutes el proyecto en modo debugging y pongas puntos de interrupción en el código del servidor y por otro lado en el navegador, pongas puntos de interrupción en el componente de React para ver cómo interactúan.
Y si hacemos un build podemos ver lo que genera:
npm run build
En este caso, también tenemos una carpeta server
dentro de la carpeta dist
, que contiene el código de las acciones de servidor.
Alucinante ¿Verdad?
En el siguiente vídeo vamos a ver como utilizarl la directiva server:defer
para hacer streaming de HTML desde el servidor, y así poder cargar partes de la página de forma asíncrona, mejorando la experiencia del usuario.