Práctica: Construye un formulario de contacto con validación

Lectura
30 min~5 min lectura

Concepto clave

En SvelteKit, los Server Actions son funciones que se ejecutan exclusivamente en el servidor, permitiendo manejar operaciones sensibles como validación de datos, acceso a bases de datos y envío de emails sin exponer lógica al cliente. Imagina un restaurante donde el cliente solo ve el menú y hace su pedido (frontend), pero toda la preparación, cocción y empaquetado ocurre en la cocina (backend). Los Server Actions son como esos procesos de cocina: seguros, eficientes y ocultos al cliente.

La validación en este contexto va más allá de simples comprobaciones en el navegador. Implementamos una validación en dos capas: primero en el cliente para experiencia de usuario (feedback inmediato) y luego en el servidor para seguridad (validación definitiva). Esto es crucial porque el usuario podría deshabilitar JavaScript o manipular las validaciones del cliente.

Cómo funciona en la práctica

Vamos a construir un formulario de contacto completo paso a paso. Primero, creamos la estructura básica del formulario en Svelte con campos para nombre, email y mensaje. Luego, implementamos validación del lado del cliente usando las capacidades nativas de HTML5 y algo de JavaScript para feedback visual.

El siguiente paso es crear la Server Action en +page.server.js. Esta función recibirá los datos del formulario, realizará validación exhaustiva (incluyendo sanitización) y luego procesará la lógica de negocio. Finalmente, conectamos el formulario del cliente con la Server Action usando el atributo action de SvelteKit, que maneja automáticamente el envío y la respuesta.

Código en acción

Primero, el formulario básico en +page.svelte:

<form method="POST" action="?/sendContact">
  <div>
    <label for="name">Nombre completo</label>
    <input 
      type="text" 
      id="name" 
      name="name" 
      required
      minlength="2"
      maxlength="100"
    >
    <span class="error">{@errors.name}</span>
  </div>
  
  <div>
    <label for="email">Email</label>
    <input 
      type="email" 
      id="email" 
      name="email" 
      required
    >
    <span class="error">{@errors.email}</span>
  </div>
  
  <div>
    <label for="message">Mensaje</label>
    <textarea 
      id="message" 
      name="message" 
      required
      minlength="10"
      maxlength="1000"
    ></textarea>
    <span class="error">{@errors.message}</span>
  </div>
  
  <button type="submit">Enviar mensaje</button>
</form>

Ahora, la Server Action en +page.server.js:

import { fail } from '@sveltejs/kit';
import { z } from 'zod';

export const actions = {
  sendContact: async ({ request }) => {
    const formData = await request.formData();
    
    // Esquema de validación con Zod
    const contactSchema = z.object({
      name: z.string()
        .min(2, 'El nombre debe tener al menos 2 caracteres')
        .max(100, 'El nombre no puede exceder 100 caracteres')
        .trim(),
      email: z.string()
        .email('Email inválido')
        .max(150, 'Email demasiado largo'),
      message: z.string()
        .min(10, 'El mensaje debe tener al menos 10 caracteres')
        .max(1000, 'El mensaje no puede exceder 1000 caracteres')
        .trim()
    });
    
    try {
      const validatedData = contactSchema.parse({
        name: formData.get('name'),
        email: formData.get('email'),
        message: formData.get('message')
      });
      
      // Aquí iría la lógica de negocio real
      // Ejemplo: await sendEmail(validatedData);
      // Ejemplo: await saveToDatabase(validatedData);
      
      console.log('Datos validados:', validatedData);
      
      return { success: true, message: 'Mensaje enviado correctamente' };
      
    } catch (error) {
      if (error instanceof z.ZodError) {
        const errors = error.flatten().fieldErrors;
        return fail(400, {
          errors: {
            name: errors.name?.[0] || '',
            email: errors.email?.[0] || '',
            message: errors.message?.[0] || ''
          }
        });
      }
      
      return fail(500, {
        errors: { general: 'Error interno del servidor' }
      });
    }
  }
};

Errores comunes

  • Confiar solo en validación del cliente: Los usuarios pueden deshabilitar JavaScript o manipular las validaciones. Siempre valida en el servidor.
  • No sanitizar los datos: Usar .trim() en strings evita espacios innecesarios y potenciales problemas de almacenamiento.
  • Manejo pobre de errores: No capturar excepciones en las Server Actions puede causar caídas silenciosas. Usa try-catch siempre.
  • Exponer detalles de error al cliente: En producción, muestra mensajes genéricos como "Error al procesar" en lugar de detalles técnicos.
  • Olvidar el atributo method="POST": Sin él, el formulario se envía por GET, exponiendo datos en la URL.

Checklist de dominio

  1. Creé un formulario con validación HTML5 básica (required, minlength, type="email")
  2. Implementé una Server Action que recibe y procesa datos del formulario
  3. Usé una librería de validación (Zod, Yup, o similar) para validación robusta en servidor
  4. Manejé errores de validación mostrando mensajes específicos por campo
  5. Saniticé los datos de entrada (trim, escape cuando sea necesario)
  6. Protegí la Server Action contra errores inesperados con try-catch
  7. Proveí feedback claro al usuario sobre el resultado del envío

Extiende el formulario de contacto con campo de teléfono y notificación por email

Objetivo: Ampliar el formulario de contacto para incluir un campo de teléfono opcional y simular el envío de un email de confirmación.

  1. Agrega el campo teléfono al formulario en +page.svelte:
    • Campo opcional (no required)
    • Validación HTML5: type="tel" y pattern para formato internacional
    • Muestra un ejemplo de formato en placeholder
  2. Actualiza la Server Action en +page.server.js:
    • Modifica el esquema Zod para incluir teléfono como campo opcional
    • Agrega validación: máximo 20 caracteres, solo números, espacios y signos +-()
    • Si hay teléfono, normalízalo (elimina espacios y caracteres especiales)
  3. Simula envío de email:
    • Crea una función simulateEmailSend que retorne una promesa
    • Simula un retraso de 1-2 segundos con setTimeout
    • Llama esta función después de la validación exitosa
    • Maneja posibles errores en el envío del email
  4. Mejora la UI:
    • Muestra un indicador de carga mientras se procesa el envío
    • Agrega un mensaje de éxito/error más detallado
    • Permite reenviar el formulario después de un error
Pistas
  • Usa z.string().optional() en Zod para campos no requeridos
  • Para el pattern del teléfono, considera pattern="[+]?[0-9\s\-()]+"
  • Usa form.processing en Svelte para mostrar indicador de carga

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.