DEV Community

Gabriel González
Gabriel González

Posted on • Updated on

Desarrollando una aplicación MERN usando Docker-compose

Este post es una taducción literal del post https://dev.to/sujaykundu777/utilizing-the-power-of-docker-while-building-mern-apps-using-mern-docker-4olb escrito por Sujay Kundu, todos los créditos son para él

Disclaimer: this post is a translation from the post https://dev.to/sujaykundu777/utilizing-the-power-of-docker-while-building-mern-apps-using-mern-docker-4olb written by Sujay Kundu, all credit goes to him

Hola a todos, en este artículo aprenderemos a contruir una aplicación MERN (MongoDB, Express, React, Node) utilizando Docker y Docker-compose.

El código de esta aplicación puede ser encontrado en https://github.com/gaabgonca/mern-docker

A gran escala, los pasos a seguir son los siguientes:

1. Creación de la estructura del app
2. Creación del servidor de express.js
3. Creación del cliente react
4. Conexión del cliente y el servidor



1. Creando la aplicación (Estructura de carpetas)

Se crea un nuevo directorio para la app

mkdir myapp
cd myapp
Enter fullscreen mode Exit fullscreen mode

Se van a separar el cliente y el servidor. Se construye el servidor primero:

mkdir server
mkdir client
cd server
Enter fullscreen mode Exit fullscreen mode



2. Creando el servidor de Express

Ahora se crea la aplicación de Node dentro del directorio servidor

Se inicializa el app usando:

npm init
Enter fullscreen mode Exit fullscreen mode

Esto crea un archivo package.json, y se dejan las configuraciones por defecto.

Instalación de dependencias

Dado que se va a utilizar Express se instala como dependencia usando npm:

npm i -S express 
Enter fullscreen mode Exit fullscreen mode

Además se instala como dependencia de desarrollo Nodemon:

npm i -D nodemon
Enter fullscreen mode Exit fullscreen mode

Dado que se está utilizando nodemon para observar los cambios, se añade un comando para correr el servidor usando nodemon en el archivo package.json

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "node server.js",
    "dev": "nodemon server.js"
  }
Enter fullscreen mode Exit fullscreen mode

Creación del servidor

Se crea un nuevo archivo server.js en el directorio /server_

// server.js
const express = require('express');
const app = express();
const PORT = 8080;

app.get('/', (req, res) => {
    res.send("Hello World ! ");
});

app.listen(PORT, function () {
    console.log(`Server Listening on ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Se ha creado un servidor, que estará escuchando en el puerto 8080. Al correrlo con npm run dev nodemon observa los cambios y los refleja.

Alt Text

Ahora, al visitar http://localhost:8080 en el explorador debe mostrarse: Hello World!

Alt Text

Conexión a MongoDB

Se instalan las dependencias moongose, que es un ODM para MongoDB, y dotenv para el manejo de variables de entorno:

npm i -S nodemon dotenv 
Enter fullscreen mode Exit fullscreen mode

Ahora se crea un directorio /src en el directorio /server, que contenga el resto de los archivos. Dentro, se crea un archivo database.js:

//database.js
const mongoose = require('mongoose');
const dotenv = require('dotenv');
dotenv.config();

// mongoose options
const options = {
  useNewUrlParser: true,
  useFindAndModify: false,
  useCreateIndex: true,
  useUnifiedTopology: true,
  autoIndex: false,
  poolSize: 10,
  bufferMaxEntries: 0
};

// mongodb environment variables
const {
    MONGO_HOSTNAME,
    MONGO_DB,
    MONGO_PORT
} = process.env;

const dbConnectionURL = {
    'LOCALURL': `mongodb://${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}`
};
mongoose.connect(dbConnectionURL.LOCALURL, options);
const db = mongoose.connection;
db.on('error', console.error.bind(console, 'Mongodb Connection Error:' + dbConnectionURL.LOCALURL));
db.once('open', () => {
     // we're connected !
     console.log('Mongodb Connection Successful');
});
Enter fullscreen mode Exit fullscreen mode

Se necesita crear un archivo .env para las variables de la base de datos (en el directorio del servidor)

MONGO_HOSTNAME=localhost
MONGO_DB=myapp_db
MONGO_PORT=27017
Enter fullscreen mode Exit fullscreen mode

Además, para usar la conexión en la aplicación express, se llama la conexión a la base de datos dentro de server.js

// Our DB Configuration
require('./src/database');
Enter fullscreen mode Exit fullscreen mode

Ahora, al correr el app estará el servidor expuesto en el puerto 8080 y la base de datos Mongo en el puerto 27017

Alt Text

Creando el modelo de los Posts

Antes de crear el primer endpoint de la API, se crea un modelo para los Posts del blog. Por simplicidad, cada post tendrá título, contenido y autor.

Se crea un nuevo directorio /models dentro del directorio /src. Dentro, se crea el archivo post.model.js

// Post.model.js
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
   title: {
       type: String,
       required: true
   },
   body: {
       type: String
   },
   author: {
       type: String
   }
});

const Post = mongoose.model("Post", postSchema);
module.exports = Post;
Enter fullscreen mode Exit fullscreen mode

Creando las rutas de la API

Ahora se crean las rutas: Se crea un nuevo directorio /routes dentro de /server. Dentro, se crea un archivo post.router.js

const express = require('express');
const postRouter = express.Router();
const Post = require('../models/post.model'); // post model

/* Get all Posts */
postRouter.get('/', (req, res, next) => {
    Post.find({} , function(err, result){
        if(err){
            res.status(400).send({
                'success': false,
                'error': err.message
            });
        }
        res.status(200).send({
            'success': true,
            'data': result
        });
    });
});

/* Get Single Post */
postRouter.get("/:post_id", (req, res, next) => {
    Post.findById(req.params.post_id, function (err, result) {
        if(err){
             res.status(400).send({
               success: false,
               error: err.message
             });
        }
        res.status(200).send({
            success: true,
            data: result
        });
     });
});


/* Add Single Post */
postRouter.post("/", (req, res, next) => {
  let newPost = {
    title: req.body.title,
    body: req.body.body,
    author: req.body.author
  };
   Post.create(newPost, function(err, result) {
    if(err){
        res.status(400).send({
          success: false,
          error: err.message
        });
    }
      res.status(201).send({
        success: true,
        data: result,
        message: "Post created successfully"
      });
  });
});

/* Edit Single Post */
postRouter.patch("/:post_id", (req, res, next) => {
  let fieldsToUpdate = req.body;
  Post.findByIdAndUpdate(req.params.post_id,{ $set: fieldsToUpdate }, { new: true },  function (err, result) {
      if(err){
          res.status(400).send({
             success: false,
            error: err.message
            });
      }
      res.status(200).send({
        success: true,
        data: result,
        message: "Post updated successfully"
        });
  });
});

/* Delete Single Post */
postRouter.delete("/:post_id", (req, res, next) => {
  Post.findByIdAndDelete(req.params.post_id, function(err, result){
      if(err){
        res.status(400).send({
          success: false,
          error: err.message
        });
      }
    res.status(200).send({
      success: true,
      data: result,
      message: "Post deleted successfully"
    });
  });
});

module.exports = postRouter;
Enter fullscreen mode Exit fullscreen mode

Ahora para usar estas rutas dentro del app, se añaden las siguientes lineas a server.js:

const bodyParser = require('body-parser');

// Routes
const postRouter = require('./src/routes/post.router');

app.use(
  bodyParser.urlencoded({
    extended: true
  })
);
app.use(bodyParser.json());

app.use('/posts', postRouter);
Enter fullscreen mode Exit fullscreen mode

Se debe instalar la dependencia body-parser:

npm install -S body-parser
Enter fullscreen mode Exit fullscreen mode

Para probar los endpoints de la API se puede utilizar la aplicación Postman. Se verifica que los endpoints funcionan:

Alt Text

Alt Text

Dockerizando Express y Mongodb

Se añade Dockerfile al directorio /server

#  Dockerfile for Node Express Backend api (development)

FROM node:current-alpine

# ARG NODE_ENV=development

# Create App Directory
RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app

# Install Dependencies
COPY package*.json ./

RUN npm ci

# Copy app source code
COPY . .

# Exports
EXPOSE 8080

CMD ["npm","start"]
Enter fullscreen mode Exit fullscreen mode

Ahora se puede construir la aplicación Express usando el siguiente comando

docker build -t node-app .

Sin embargo, esto solo correrá la aplicación express sin MongoDB. Por esto se necesita un archivo

docker compose

.

Se crea un archivo docker-compose.yml y se añade lo siguiente:

version: '3.3'

services:
    webapp-server:
      build:
        context: .
        dockerfile: Dockerfile
      image: myapp-server-img
      container_name: myapp-node-express
      volumes:
       - .:/usr/src/app
       - /usr/src/app/node_modules
      ports:
        - "8080:8080"
      depends_on:
        - mongo
      env_file: .env
      environment:
        - MONGO_HOSTNAME=$MONGO_HOSTNAME
        - MONGO_PORT=$MONGO_PORT
        - MONGO_DB=$MONGO_DB
    mongo:
      image: mongo
      container_name: myapp-mongodb
      ports:
        - "27017:27017"

Enter fullscreen mode Exit fullscreen mode

Además se debe cambiar la url de conexión de localhost a mongo. Para ello,
en el archivo .env se edita MONGO_HOSTNAME=mongo.

Para correr el app usando docker-compose:

Se construyen las imágenes con:

docker-compose build

Se corren los contenedores con:

docker-compose up

Se verifica que todo funciona:

Alt Text



3. Creando el cliente React

Ahora se crea y se configura el front-end para la aplicación. Se inicializa una aplicación react usando npx

npx create-react-app client

Para correr el app creada en el directorio cliente se usa:

yarn start

Esto inicia un servidor de desarrollo en el puerto 3000. Se comprueba en el navegador accediendo a http://localhost:3000

Alt Text

Dockerizando la aplicación React

Se crea un Dockerfile en el directorio /client


# Dockerfile for client

# Stage 1: Build react client
FROM node:current-alpine

# Working directory be app
WORKDIR /usr/app

COPY package*.json ./

# Install dependencies
RUN yarn install

# copy local files to app folder
COPY . .

EXPOSE 3000

CMD ["yarn","start"]

Enter fullscreen mode Exit fullscreen mode

Se construye el contenedor usando el comando

docker build -t myapp-react:v1 .

Se corre el contenedor usando el comando

docker run -it myapp-react:v1

. Se verifica accediendo en el explorador a http://localhost:3000/

A este punto se tienen contenedores independientes para el servidor y para el cliente, pero no estan interactuando entre sí. Para solucionar esto se utiliza docker-compose.



4. Conectando el cliente y servidor usando docker-compose

Para esto, se debe avisar al servidor de la existencia del cliente

En /myapp/server/server.js se añade:

// will redirect all the non-api routes to react frontend
router.use(function(req, res) {
    res.sendFile(path.join(__dirname, '../client','build','index.html'));
});
Enter fullscreen mode Exit fullscreen mode

Además se debe avisar al cliente react que redireccione las solicitudes a la API al puerto 8080 (donde está el servidor).

En /myapp/client/package.json se añade:

 "proxy": "http://server:8080"
Enter fullscreen mode Exit fullscreen mode

Ahora se crea un archivo docker-compose.yml en el directorio /myapp, que interactue con los Dockerfiles del cliente y del servidor y que cree una red entre los contenedores:

version: '3.3'

services:
  server:
    build:
      context: ./server
      dockerfile: Dockerfile
    image: myapp-server
    container_name: myapp-node-server
    command: /usr/src/app/node_modules/.bin/nodemon server.js
    volumes:
      - ./server/:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - "8080:8080"
    depends_on:
      - mongo
    env_file: ./server/.env
    environment:
      - NODE_ENV=development
    networks:
      - app-network
  mongo:
    image: mongo
    volumes:
      - data-volume:/data/db
    ports:
      - "27017:27017"
    networks:
      - app-network
  client:
    build:
      context: ./client
      dockerfile: Dockerfile
    image: myapp-client
    stdin_open: true
    container_name: myapp-react-client
    command: yarn start
    volumes:
      - ./client/:/usr/app
      - /usr/app/node_modules
    depends_on:
      - server
    ports:
      - "3000:3000"
    networks:
      - app-network

networks:
    app-network:
        driver: bridge

volumes:
    data-volume:
    node_modules:
    web-root:
      driver: local
Enter fullscreen mode Exit fullscreen mode

Lo que esto hace es correr los servicios en paralelo. El backend express corre en el puerto 8080, la base de datos Mongo en el puerto 27017 y el cliente React en el puerto 3000. Para correr los contenedores se utiliza el siguiente comando:

docker-compose up --build

En este artículo se pudo ver como dockerizar una aplicación MERN con contenedores separados para cilente y servidor, utilizando docker-compose.

Top comments (0)