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
Se van a separar el cliente y el servidor. Se construye el servidor primero:
mkdir server
mkdir client
cd server
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
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
Además se instala como dependencia de desarrollo Nodemon:
npm i -D nodemon
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"
}
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}`);
});
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.
Ahora, al visitar http://localhost:8080 en el explorador debe mostrarse: Hello World!
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
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');
});
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
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');
Ahora, al correr el app estará el servidor expuesto en el puerto 8080 y la base de datos Mongo en el puerto 27017
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;
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;
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);
Se debe instalar la dependencia body-parser:
npm install -S body-parser
Para probar los endpoints de la API se puede utilizar la aplicación Postman. Se verifica que los endpoints funcionan:
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"]
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"
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:
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
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"]
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'));
});
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"
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
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)