mvrcoag@gmail.com

Next JS y arquitectura modular - 04/01/2024

Arquitectura modular en Next JS, te cuento mi experiencia

¿Arquitectura? ¿Módulos? ¿Para qué complicarse?

Primero que nada, ¿Qué es Next JS? En su propio sitio web se define como The React Framework for the Web, siendo entonces un framework de React (el más popular de hecho).

¿Y cuál es el problema? Bueno, el problema es que React da demasiada libertad al programador. Pero, ¿Realmente esto es un problema?

Analizando la problemática

El problema no es ni de React, ni de la gran libertad que otorga al desarrollador al momento de programar, no. El problema está en cómo el desarrollador abusa de esta libertad.

Analicemos, por ejemplo, un código de PHP que abusa de la libertad que el lenguaje otorga:

<?php

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $conn = mysqli_connect("localhost", "root", "password", "mi_base_de_datos");

    if ($conn) {
        $nombre = $_POST['nombre'];
        $apellido = $_POST['apellido'];
        $edad = $_POST['edad'];

        $query = "INSERT INTO usuarios (nombre, apellido, edad) VALUES ('$nombre', '$apellido', $edad)";

        if (mysqli_query($conn, $query)) {
            echo "Usuario registrado correctamente.";
        } else {
            echo "Error al registrar usuario: " . mysqli_error($conn);
        }

        mysqli_close($conn);
    } else {
        die("Error de conexión a la base de datos");
    }
}

?>

<!DOCTYPE html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <title>Registro de Usuario</title>
</head>
<body>
    <form method="post" action="">
        <label for="nombre">Nombre:</label>
        <input type="text" name="nombre" required>
        <br>
        <label for="apellido">Apellido:</label>
        <input type="text" name="apellido" required>
        <br>
        <label for="edad">Edad:</label>
        <input type="number" name="edad" required>
        <br>
        <input type="submit" value="Registrar">
    </form>
</body>
</html>

Aunque en primera instancia funciona, aquí podemos comenzar a notar malas prácticas:

Ahora, ¿Esto significa que PHP es un mal lenguaje? ¿O significa que todo el código hecho con PHP tendrá malas prácticas? No, ya que se puede generar muy buen código usando PHP, pero esto es responsabilidad del programador.

Por cierto, no tengo nada en contra de PHP. Yo amo PHP, cuando se hace buen PHP ❤️.

Bien, ahora veamos el siguiente código de React que encontré por ahí:

import React, { useState } from "react";

const NameAndAgeForm = () => {
  const [data, setData] = useState({ name: "", age: 0 });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setData((prevData) => ({ ...prevData, [name]: value }));
  };

  const handleFormSubmit = (e) => {
    e.preventDefault();

    if (data.name && data.age > 0) {
      alert(`User registered: ${data.name}, ${data.age} years`);
    } else {
      alert("Error registering user: verify the fields.");
    }
  };

  return (
    <div>
      <form onSubmit={handleFormSubmit}>
        <label>Name:</label>
        <input
          type="text"
          name="name"
          value={data.name}
          onChange={handleInputChange}
          required
        />
        <br />
        <label>Age:</label>
        <input
          type="number"
          name="age"
          value={data.age}
          onChange={handleInputChange}
          required
        />
        <br />
        <button type="submit">Register</button>
      </form>
    </div>
  );
};

export default NameAndAgeForm;

¿Cuál es el problema aquí? Si bien no estamos interactuando directamente con la base de datos, como sí lo hacíamos con el ejemplo de PHP, sí estamos cayendo en las mismas malas prácticas:

¿Entonces el problema es con React o con Next JS?

Bueno, a decir verdad, el problema no es ninguno de los dos, sino las malas prácticas que el desarrollador tenga con las distintas tecnologías, así sea React, Next, Remix, Java o Laravel.

Habiendo visto lo anterior, ¿Qué tal si analizamos una propuesta de estructura de proyecto con arquitectura modular para Next JS? Vamos.

Arquitectura modular en Next JS

Partamos de identificar dos entidades: módulos y funcionalidades.

Cada módulo tendrá una o más funcionalidades, por ejemplo: Tenemos el módulo User que tiene la funcionalidad save y delete.

- User
    - save
    - delete

Ahora bien, partamos de una estructura de proyecto básica de Next JS:

(src)
- components
- pages
- utils
- styles

A partir de aquí podemos comenzar a agregar nuestra propia estructura. Comencemos creando una carpeta modules que será donde se vayan a crear nuestros módulos.

Dentro de modules y siguiendo el mismo ejemplo de User, crearemos dentro una carpeta user, aquí se guardarán las funcionalidades de nuestro módulo User.

Dentro de user crearemos dos carpetas más: save y delete, cada una haciendo referencia a cada una de las funcionalidades de nuestro módulo.

El resultado debería ser algo como:

(src)
- components
- modules
    - user
        - save
        - delete
- pages
- utils
- styles

Ahora bien, ya tenemos la estructura del módulo, ¿Pero cómo deben lucir los archivos dentro de cada carpeta de funcionalidad? Fácil, haremos uso de hasta 6 archivos según nuestro uso:

Hagamos el ejemplo con nuestra funcionalidad save, ¿Qué necesitamos? Bueno, necesitamos de una vista, esta vista tendrá un formulario mismo que consumirá su propio custom hook, y este custom hook hará uso de un esquema para validar la entrada de datos. Ya lo tenemos, los archivos deberán ser:

(src/modules/user/save)
- useUserSave.ts
- useUserSaveForm.ts
- useUserSave.schema.ts
- UserSaveForm.tsx
- UserSaveView.tsx

¿Notas un patrón? Exacto, cada archivo es autodescriptivo tanto de su módulo, funcionalidad y responsabilidad, con lo que ya tendremos una mejor segmentación del código, al igual que responsabilidades bien definidas y modularización para hacer uso de esta lógica en alguna otra parte de nuestra aplicación.

Ahora podemos ver la composición inicial de cada uno de los archivos:

Para estos ejemplos se ha usado formik para gestión de formularios, zod para validaciones de datos y zod-formik-adapter para la adaptación del esquema de zod en formik. Puedes usar estas herramientas o si lo deseas usar las que tu prefieras. Lo importante aquí es cómo separemos y modularicemos nuestros componentes.

useUserSave.ts
// hook para gestionar datos de la vista
export const useUserSave = () => {
  return {};
};
useUserSaveForm.ts
// hook para gestionar datos del formulario
import { toFormikValidationSchema } from "zod-formik-adapter";
import { useFormik } from "formik";
import { userSaveSchema, type UserSaveType } from "./useUserSave.schema.ts";

const initialValues: UserSaveType = {
  name: "",
};

export const useUserSaveForm = () => {
  const formik = useFormik({
    initialValues,
    validationSchema: toFormikValidationSchema(userSaveSchema),
    onSubmit: (values: UserSaveType) => {
      console.log(values);
    },
  });

  return {
    formik,
  };
};
useUserSave.schema.ts
// esquema para validar los datos desde el hook del formulario
import { z } from "zod";

export const userSaveSchema = z.object({
  name: z.string({
    required_error: "El nombre es requerido",
  }),
});

export type UserSaveType = z.infer<typeof userSaveSchema>;
UserSaveForm.tsx
// ui de formulario, misma que consumirá useUserSaveForm
import { useUserSaveForm } from "./useUserSaveForm";

export const UserSaveForm = () => {
  const { formik } = useUserSaveForm();

  return null;
};
UserSaveView.tsx
// ui de la vista, misma que consumirá useUserSave
export const UserSaveView = () => {
  return null;
};

¿Tengo que crear la estructura manualmente?

No, para nada. Dada mi necesidad de tener una mejor estructura no solo en proyectos de Next JS, sino en el ecosistema de React en general, he creado magen. Magen (Module Architecture Generator) es un generador de módulos para crear aplicaciones de react. Esta librería te ayuda creando código de base tanto para el frontend como para el backend, pero no hay mejor forma de entenderlo que probandolo.

Para ello puedes ejecutar:

npx magen@latest

No hace falta instalarlo en local ni nada por el estilo. Después de ejecutar este comando magen te hará una seríe de preguntas de acuerdo a las necesidades del módulo que vayas a crear y listo, tendrás el código base para desarrollar la nueva funcionalidad de tu código con una arquitectura modular.

Uso de magen en terminal Uso de magen en terminal

Estructura de carpetas magen Estructura de carpetas magen

Conclusión

Hemos visto los beneficios que nos puede traer utilizar una arquitectura modular en nuestros proyectos de Next JS, además de que hemos visto cómo magen nos puede ayudar con este proposito.

El código de magen puede ser encontrado en este reporsitorio de GitHub. Sientete libre de hacer un pull request, ¡Con gusto lo revisaremos!