Skip to content

2 El patrón MVC

Rutas

Cuando se diseña una API REST y tenemos muchos endpoints con diferentes métodos nuestra archivo javascript se vuelve engorroso y bastante pesado y difícil de leer, por lo que Express nos permite trabajar con diferentes rutas.

Una ruta se considera un acceso a nuestro recurso para poder hacer las diferentes tipos de operaciones a través de los métodos. Para crear una ruta se usa el método Router() de Express, luego haciendo uso de los métodos podemos realizar las operaciones necesarias:

const movieRouter = Router()

movieRouter.get('/', (req, res) => {

})

movieRouter.get('/:id', (req, res) => {

})

movieRouter.post('/', (req, res) => {

})

movieRouter.patch('/:id', (req, res) => {

})

movieRouter.delete('/:id', (req, res) => {

})

export default movieRouter

Una vez creada la ruta, debemos indicar a nuestra app que cada vez que se realice una petición a /movies acceda a la ruta creada:

app.use('/movies', moviesRouter)

Hacemos uso del método use de nuestra app porque va a utilizar la ruta para cualquiera de los métodos de la petición.

Sugerencia

Se recomienda que las rutas se separen y se introduzcan en una carpeta, que se llame por ejemplo routes.

Patrón MVC

El patrón MVC (Modelo-Vista-Controlador) es un patrón que separa los datos, la lógica de negocio y la interfaz de usuario de una aplicación. Este patrón ayuda a mejorar la modularidad, la mantenibilidad y la escalabilidad de las aplicaciones.

El patrón MVC consta de tres componentes principales:

  • Modelo: Representa los datos de una aplicación.
  • Vista: Representa la interfaz de usuario de una aplicación.
  • Controlador: Se encarga de gestionar las interacciones entre el modelo y la vista.

La siguiente figura ilustra la relación entre los tres componentes del patrón MVC:

Patrón MVC

Figura 1 - Patrón MVC

El patrón MVC ofrece una serie de ventajas, entre las que se incluyen:

  • Mejora la modularidad: El patrón MVC separa los datos, la lógica de negocio y la interfaz de usuario en componentes independientes. Esto facilita la reutilización de los componentes y la modificación de la aplicación sin afectar a otros componentes.
  • Mejora la mantenibilidad: El patrón MVC facilita la localización de los errores y la realización de cambios en la aplicación.
  • Mejora la escalabilidad: El patrón MVC facilita la adición de nuevas características y el aumento de la capacidad de la aplicación.

El patrón MVC se puede considerar tanto como patrón de diseño como de arquitectura. Los programadores no se poden de acuerdo en cómo clasificarlo.

Modelo

El modelo es la parte del patrón que se encarga de la parte lógica del negocio. Se encarga de comunicar directamente con la base de datos en caso de que lo haya.

export class MovieModel {
    static async getAll({ genre }) {
        if (genre) {
            const filteredMovies = movies.filter(movie => movie.genre.some(g => g.toLowerCase() === genre.toLowerCase()))

            return filteredMovies
        }

        return movies
    }

    static async getById({ id }) {
        return movies.find(movie => movie.id === id)
    }

    static async create({ input }) {
        const newMovie = {
            id: randomUUID(),
            ...input
        }


        movies.push(newMovie)

        return newMovie
    }

    static async delete({ id }) {
        const movieIndex = movies.findIndex(movie => movie.id === id)
        if (movieIndex === -1) return false

        movies.slice(movieIndex, 1)
        return true
    }

    static async update({ id, input }) {
        const movieIndex = movies.findIndex(movie => movie.id === id)
        if (movieIndex === -1) return false

        movies[movieIndex] = {
            ...movies[movieIndex],
            ...input
        }

        return movies[movieIndex]
    }
}

Controlador

El controlador es la parte del patrón que se encarga de hacer la comunicación entre el modelo y la vista. Se encarga de realizar las peticiones al modelo y mostrarle a la vista los diferentes datos:

export class MoviesController {
    static async getAll(req, res) {
        const { genre } = req.query

        const movies = await MovieModel.getAll({ genre })

        res.send(movies)
    }

    static async getById(req, res) {
        const { id } = req.params
        const movie = await MovieModel.getById({ id })

        if (movie) return res.json(movie)

        res.status(404).json({ message: 'Movie not found' })
    }

    static async create(req, res) {

        const result = validateMovie(req.body)

        if (result.error) {
            return res.status(400).json({ error: JSON.parse(result.error.message) })
        }


        const newMovie = await MovieModel.create({ input: result.data })


        res.status(201).json(newMovie) // actualizar la caché del cliente
    }

    static async delete(req, res) {
        const { id } = req.params
        const result = await MovieModel.delete({ id })

        if (result === false) {
            return res.status(404).json({ message: 'Movie not found' })
        }


        return res.json({ message: 'Movie deleted' })
    }

    static async update(req, res) {
        const { id } = req.params
        const result = validatePartialMovie(req.body)

        if (!result.success) {
            return res.status(400).json({ error: JSON.parse(result.error.message) })
        }


        if (movieIndex === -1) {
            return res.status(404).json({ message: 'Movie not found' })
        }

        const updateMovie = await MovieModel.update({ id, input: result.data })

        return res.json(updateMovie)
    }
}

Vista

La vista es la parte del patrón que se encarga de interactuar con el usuario. Desde aquí, el usuario solicita peticiones que son procesadas por el controlador que devuelve el resultado a la vista para trabajar con ellas. Un buen ejemplo de una vista, puede ser un formulario para añadir los datos a una base de datos.

Validaciones

Cuando se trata de validaciones se recomienda que se realicen en las tres capas de la aplicación, aunque no las mismas validaciones si no las validaciones que corresponda en cada caso.

En el modelo se debería validar las partes relacionadas con la seguridad y la base de datos. Mientras que en el controlador se debería comprobar si la estructura de los datos es correcta y en la vista se debería validar si los campos son requeridos y otras cosas como su rango de valores, etc.

Hacerlo así, requiere que ciertas peticiones lleguen limpias a su capa correspondiente y no tarden en ser validadas.