One place for hosting & domains

      Test a Node RESTful API with Mocha and Chai


      Introduction

      I still remember the satisfaction of being finally able to write the backend part of a bigger app in node and I am sure many of you do it too.

      And then? We need to make sure our app behaves the way we expect and one of the strongly suggested methodologies is software testing. Software testing is crazily useful whenever a new feature is added to the system: Having the test environment already set up which can be run with a single command helps to figure out whether a new feature introduces new bugs.

      In the past, we’ve worked on building a RESTful Node API and authenticating a Node API.

      In this tutorial we are going to write a simple RESTful API with Node.js and use Mocha and Chai to write tests against it. We will test CRUD operations on a bookstore.

      As usual you can build the app step-by-step throughout the tutorial or directly get it on github.

      Mocha: Testing Environment

      Mocha is a javascript framework for Node.js which allows Asynchronous testing. Let’s say it provides the environment in which we can use our favorite assertion libraries to test the code.

      mocha-homepage.

      Mocha comes with tons of great features, the website shows a long list but here are the ones I like the most:

      • simple async support, including promises.
      • async test timeout support.
      • before, after, before each, after each hooks (very useful to clean the environment where each test!).
      • use any assertion library you want, Chai in our tutorial.

      Chai: Assertion Library

      So with Mocha we actually have the environment for making our tests but how do we do test HTTP calls for example? Moreover, How do we test whether a GET request is actually returning the JSON file we are expective, given a defined input? We need an assertion library, that’s why mocha is not enough.

      So here it is Chai, the assertion library for the current tutorial:

      chai homepage

      Chai shines on the freedom of choosing the interface we prefer: “should”, “expect”, “assert” they are all available. I personally use should but you are free to check it out the API and switch to the others two. Lastly Chai HTTP addon allows Chai library to easily use assertions on HTTP requests which suits our needs.

      Prerequisites

      • Node.js: a basic understanding of node.js and is recommended as i wont go too much into detail on building a RESTful API.
      • POSTMAN for making fast HTTP requests to the API.
      • ES6 syntax: I decided to use the latest version of Node (6.*.*) which has the highest integration of ES6 features for better code readibility. If you are not familiar with ES6 you can take a look at the great scotch articles (Pt.1 , Pt.2 and Pt.3) about it but do not worry I am going to spend a few words whenever we encount some “exotic” syntax or declaration.

      Time to setup our Bookstore!

      Project setup

      Directory Structure

      Here is the project directory for our API, something you must have seen before:

      -- controllers 
      ---- models
      ------ book.js
      ---- routes
      ------ book.js
      -- config
      ---- default.json
      ---- dev.json
      ---- test.json
      -- test
      ---- book.js
      package.json
      server.json
      

      Notice the /config folder containing 3 JSON files: As the name suggests, they contain particular configurations for a specific purpose.

      In this tutorial we are going to switch between two databases, one for development and one for testing purposes, thus the files contain the mongodb URI in JSON format:

      dev.json AND default.json

      { "DBHost": "YOUR_DB_URI" }
      

      test.json

      { "DBHost": "YOUR_TEST_DB_URI" }
      

      NB: default.json is optional however let me highlight that files in the config directory are loaded starting from it. For more information about the configuration files (config directory, file order, file format etc.) check out this link.

      Finally, notice /test/book.js, that’s where we are going to write our tests!

      Package.json

      Create the package.json file and paste the following code:

      {
        "name": "bookstore",
        "version": "1.0.0",
        "description": "A bookstore API",
        "main": "server.js",
        "author": "Sam",
        "license": "ISC",
        "dependencies": {
          "body-parser": "^1.15.1",
          "config": "^1.20.1",
          "express": "^4.13.4",
          "mongoose": "^4.4.15",
          "morgan": "^1.7.0"
        },
        "devDependencies": {
          "chai": "^3.5.0",
          "chai-http": "^2.0.1",
          "mocha": "^2.4.5"
        },
        "scripts": {
          "start": "SET NODE_ENV=dev && node server.js",
          "test": "mocha --timeout 10000"
        }
      }
      

      Again the configuration should not surprise anyone who wrote more than a server with node.js, the test-related packages mocha, chai, chai-http are saved in the dev-dependencies (flag --save-dev from command line) while the scripts property allows for two different ways of running the server.

      To run mocha I added the flag --timeout 10000 because I fetch data from a database hosted on mongolab so the default 2 seconds may not be enough.

      Congrats! You made it through the boring part of the tutorial, now it is time to write the server and test it.

      The server

      Main

      Let’s create the file server.js in the root of the project and paste the following code:

      
      let express = require('express');
      let app = express();
      let mongoose = require('mongoose');
      let morgan = require('morgan');
      let bodyParser = require('body-parser');
      let port = 8080;
      let book = require('./app/routes/book');
      let config = require('config'); //we load the db location from the JSON files
      //db options
      let options = { 
                      server: { socketOptions: { keepAlive: 1, connectTimeoutMS: 30000 } }, 
                      replset: { socketOptions: { keepAlive: 1, connectTimeoutMS : 30000 } } 
                    }; 
      
      //db connection      
      mongoose.connect(config.DBHost, options);
      let db = mongoose.connection;
      db.on('error', console.error.bind(console, 'connection error:'));
      
      //don't show the log when it is test
      if(config.util.getEnv('NODE_ENV') !== 'test') {
          //use morgan to log at command line
          app.use(morgan('combined')); //'combined' outputs the Apache style LOGs
      }
      
      //parse application/json and look for raw text                                        
      app.use(bodyParser.json());                                     
      app.use(bodyParser.urlencoded({extended: true}));               
      app.use(bodyParser.text());                                    
      app.use(bodyParser.json({ type: 'application/json'}));  
      
      app.get("/", (req, res) => res.json({message: "Welcome to our Bookstore!"}));
      
      app.route("/book")
          .get(book.getBooks)
          .post(book.postBook);
      app.route("/book/:id")
          .get(book.getBook)
          .delete(book.deleteBook)
          .put(book.updateBook);
      
      
      app.listen(port);
      console.log("Listening on port " + port);
      
      module.exports = app; // for testing
      

      Here are the key concepts:

      • We require the module config to access the configuration file named as the NODE_ENV content to get the mongo db URI parameter for the db connection. This helps us to keep the “real” database clean by testing on another database hidden to our app future users.
      • The enviroment variable NODE_ENV is test against test to disable morgan log in the command line or it would interfere with the test output.
      • The last line of code exports the server for testing purposes.
      • Notice the variables definition using let which makes the variable enclosed to the nearest enclosing block or global if outside any block.

      The remaining lines of codes are nothing new, we simply go through requiring all the necessary modules, define the header options for the communication with the server, craete the specific roots and eventually let the server listen on a defined port.

      Model and Routes

      Time for our book model! Create a file in /app/model/ called book.js and paste the following code:

      let mongoose = require('mongoose');
      let Schema = mongoose.Schema;
      
      //book schema definition
      let BookSchema = new Schema(
        {
          title: { type: String, required: true },
          author: { type: String, required: true },
          year: { type: Number, required: true },
          pages: { type: Number, required: true, min: 1 },
          createdAt: { type: Date, default: Date.now },    
        }, 
        { 
          versionKey: false
        }
      );
      
      // Sets the createdAt parameter equal to the current time
      BookSchema.pre('save', next => {
        now = new Date();
        if(!this.createdAt) {
          this.createdAt = now;
        }
        next();
      });
      
      //Exports the BookSchema for use elsewhere.
      module.exports = mongoose.model('book', BookSchema);
      

      Our book schema has a title, author, the number of pages, the publication year and the date of creation in the db. I set the versionKey to false since it’s useless for the purpose of the tutorial.

      NB: the exotic callback syntax in the .pre() function is an arrow function, a function who has a shorter syntax which, according to the definiton on MDN , “lexically binds the this value (does not bind its own this, arguments, super, or new.target). Arrow functions are always anonymous”.

      Well, pretty much all we need to know about the model so let’s move to the routes.

      in /app/routes/ create a file called book.js and paste the following code:

      let mongoose = require('mongoose');
      let Book = require('../models/book');
      
      /*
       * GET /book route to retrieve all the books.
       */
      function getBooks(req, res) {
          //Query the DB and if no errors, send all the books
          let query = Book.find({});
          query.exec((err, books) => {
              if(err) res.send(err);
              //If no errors, send them back to the client
              res.json(books);
          });
      }
      
      /*
       * POST /book to save a new book.
       */
      function postBook(req, res) {
          //Creates a new book
          var newBook = new Book(req.body);
          //Save it into the DB.
          newBook.save((err,book) => {
              if(err) {
                  res.send(err);
              }
              else { //If no errors, send it back to the client
                  res.json({message: "Book successfully added!", book });
              }
          });
      }
      
      /*
       * GET /book/:id route to retrieve a book given its id.
       */
      function getBook(req, res) {
          Book.findById(req.params.id, (err, book) => {
              if(err) res.send(err);
              //If no errors, send it back to the client
              res.json(book);
          });        
      }
      
      /*
       * DELETE /book/:id to delete a book given its id.
       */
      function deleteBook(req, res) {
          Book.remove({_id : req.params.id}, (err, result) => {
              res.json({ message: "Book successfully deleted!", result });
          });
      }
      
      /*
       * PUT /book/:id to updatea a book given its id
       */
      function updateBook(req, res) {
          Book.findById({_id: req.params.id}, (err, book) => {
              if(err) res.send(err);
              Object.assign(book, req.body).save((err, book) => {
                  if(err) res.send(err);
                  res.json({ message: 'Book updated!', book });
              });    
          });
      }
      
      //export all the functions
      module.exports = { getBooks, postBook, getBook, deleteBook, updateBook };
      

      Here the key concepts:

      • The routes are no more than standard routes, GET, POST, DELETE, PUT to perform CRUD operations on our data.
      • In the function updatedBook() we use Object.assign, a new function introduced in ES6 which, in this case, overrides the common properties of book with req.body while leaving untouched the others.
      • At the end we export the object using a faster syntax which pairs key and value to avoid useless repetitions.

      We finished this section and actually we have a working app!

      A Naive Test

      Now let’s run the app and open POSTMAN to send HTTP request to the server and check if everything is working as expected.

      in the command line run

      npm start
      

      GET /book

      in POSTMAN run the GET request and, assuming the database contains books, here is the result:

      The server correctly returned the book list in my database.

      POST /book

      Let’s add a book and POST to the server:

      It seems the book was perfectly added. The server returned the book and a message confirming it was added in our bookstore. Is it true? Let’s send another GET request and here is the result:

      Awesome it works!

      PUT /book/:id

      Let’s update a book by changing the page and check the result:

      Great! PUT also seems to be working so let’s send another GET request to check all the list:

      All is running smoothly…

      GET /book/:id

      Now let’s get a single book by sending the id in the GET request and then delete it:

      As it returns the correct book let’s try now to delete it:

      DELETE /book/:id

      Here is the result of the DELETE request to the server:

      Even the last request works smoothly and we do not need to doublecheck with another GET request as we are sending the client some info from mongo (result property) which states the book was actually deleted.

      By doing some test with POSTMAN the app happened to behave as expected right? So, would you shoot it to your clients?

      Let me reply for you: NO!!

      Ours is what I called a naive test because we simply tried few operations without testing strange situations that may happen: A post request without some expected data, a DELETE with a wrong id as parameter or even without id to name few.

      This is obviously a simple app and if we were lucky enough, we coded it without introducing bugs of any sort, but what about a real-world app? Moreover, we spent time to run with POSTMAN some test HTTP requests so what would happen if one day we had to change the code of one of those? Test them all again with POSTMAN? Have you started to realize this is not an agile approach?

      This is nothing but few situations you may encounter and you already encountered in your journey as a developer, luckily we have tools to create tests which are always available and can be launched with a single comman line.

      Let’s do something better to test our app!

      A Better Test

      First, let’s create a file in /test called book.js and paste the following code:

      //During the test the env variable is set to test
      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      //Require the dev-dependencies
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      //Our parent block
      describe('Books', () => {
          beforeEach((done) => { //Before each test we empty the database
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
      /*
        * Test the /GET route
        */
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
              chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
      
      });
      

      Wow that’s a lot of new things, let’s dig into it:

      1. You must have noticed the we set the NODE_ENV variable to test, by doing so we change the configuration file to be loaded so the server will connect to the test database and avoid morgan logs in the cmd.
      2. We required the dev-dependencies modules and server itself (Do you remember we exported it by module.exports?).

      3. We defined should by running chai.should() to style our tests on the HTTP requests result, then we told chai to use chai HTTP.

      So it starts with “describe” blocks of code for better organizing your assertions and this organization will reflect in the output at command line as we will see later.

      beforeEach is a block of code that is going to run before each the describe blocks on the same level. Why we did that? Well we are going to remove any book from the database to start with an empty bookstore whenever a test is run.

      Test the /GET route

      And here it comes the first test, chai is going to perform a GET request to the server and the assertions on the res variable will satisfy or reject the first parameter of the the it block it should GET all the books. Precisely, given the empty bookstore the result of the request should be:

      1. Status 200.
      2. The result should be an array.
      3. Since the bookstore is empty, we presumed the length is equal to 0.

      Notice that the syntax of should assertions is very intituitive as it is similar as a natural language statement.

      Now, in the command line run:

      “`javascript npm test ”`

      and here it is the output:

      The test passed and the output reflects the way we organized our code with blocks of describe.

      Test the /POST route

      Now let’s check our robust is our API, suppose we are trying to add a book with missing pages field passed to the server: The server should not respond with a proper error message.

      Copy and paste the following code in the test file:

      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      
      describe('Books', () => {
          beforeEach((done) => {
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
              chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
        /*
        * Test the /POST route
        */
        describe('/POST book', () => {
            it('it should not POST a book without pages field', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954
                }
              chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('errors');
                        res.body.errors.should.have.property('pages');
                        res.body.errors.pages.should.have.property('kind').eql('required');
                    done();
                  });
            });
      
        });
      });
      
      

      Here we added the test on an incomplete /POST request, let’s analyze the assertions:

      1. Status should be 200.
      2. The response body should be an object.
      3. One of the body properties should be errors.
      4. Errors should have have the missing field pages as property.
      5. Finally pages should have the property kind equal to required in order to highlight the reason why we got a negative answer from the server.

      NB notice that we send the book along with the POST request by the .send() function.

      Let’s run the same command again and here is the output:

      Oh Yeah our test test is correct!

      Before writing a new test let me precise two things:

      1. First of all, why the server response structured that way? If you read the callback function for the /POST route, you will notice that in case of missing required fields, the server sends back the error message from mongoose. Try with POSTMAN and check the response.
      2. In case of missing fields we still return a status of 200, this is for simplicity as we are just learning to test our routes. However I suggest to return a status of 206 Partial Content instead.

      Let’s send a book with all the required fields this time. Copy and paste the following code in the test file:

      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      
      describe('Books', () => {
          beforeEach((done) => {
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
              chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
        /*
        * Test the /POST route
        */
        describe('/POST book', () => {
            it('it should not POST a book without pages field', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954
                }
              chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('errors');
                        res.body.errors.should.have.property('pages');
                        res.body.errors.pages.should.have.property('kind').eql('required');
                    done();
                  });
            });
            it('it should POST a book ', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954,
                    pages: 1170
                }
              chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('message').eql('Book successfully added!');
                        res.body.book.should.have.property('title');
                        res.body.book.should.have.property('author');
                        res.body.book.should.have.property('pages');
                        res.body.book.should.have.property('year');
                    done();
                  });
            });
        });
      });
      
      

      This time we expect a returning object with a message saying we succesfully added the book and the book itself (remember with POSTMAN?). You should be now quite familiar with the assertions I made so there is no need for going into detail. Instead, run the command again and here is the output:

      Smooth~

      Test /GET/:id Route

      Now let’s create a book, save it into the database and use the id to send a GET request to the server. Copy and paste the following code in the test file:

      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      
      describe('Books', () => {
          beforeEach((done) => {
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
                  chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
        describe('/POST book', () => {
            it('it should not POST a book without pages field', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('errors');
                        res.body.errors.should.have.property('pages');
                        res.body.errors.pages.should.have.property('kind').eql('required');
                    done();
                  });
            });
            it('it should POST a book ', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954,
                    pages: 1170
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('message').eql('Book successfully added!');
                        res.body.book.should.have.property('title');
                        res.body.book.should.have.property('author');
                        res.body.book.should.have.property('pages');
                        res.body.book.should.have.property('year');
                    done();
                  });
            });
        });
       /*
        * Test the /GET/:id route
        */
        describe('/GET/:id book', () => {
            it('it should GET a book by the given id', (done) => {
                let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
                book.save((err, book) => {
                    chai.request(server)
                  .get('/book/' + book.id)
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('title');
                        res.body.should.have.property('author');
                        res.body.should.have.property('pages');
                        res.body.should.have.property('year');
                        res.body.should.have.property('_id').eql(book.id);
                    done();
                  });
                });
      
            });
        });
      });
      
      

      Through the assertions we made sure the server returned all the fields and the right book testing the two ids together. Here is the output:

      Have you noticed that by testing single routes within independent blocks we provide a very clear output? Also, isn’t it so efficient? We wrote several tests that can be repeated with a single command line, once and for all.

      Test the /PUT/:id Route

      Time for testing an update on one of our books, we first save the book and then update the year it was published. So, copy and paste the following code:

      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      
      describe('Books', () => {
          beforeEach((done) => {
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
                  chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
        describe('/POST book', () => {
            it('it should not POST a book without pages field', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('errors');
                        res.body.errors.should.have.property('pages');
                        res.body.errors.pages.should.have.property('kind').eql('required');
                    done();
                  });
            });
            it('it should POST a book ', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954,
                    pages: 1170
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('message').eql('Book successfully added!');
                        res.body.book.should.have.property('title');
                        res.body.book.should.have.property('author');
                        res.body.book.should.have.property('pages');
                        res.body.book.should.have.property('year');
                    done();
                  });
            });
        });
        describe('/GET/:id book', () => {
            it('it should GET a book by the given id', (done) => {
                let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
                book.save((err, book) => {
                    chai.request(server)
                  .get('/book/' + book.id)
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('title');
                        res.body.should.have.property('author');
                        res.body.should.have.property('pages');
                        res.body.should.have.property('year');
                        res.body.should.have.property('_id').eql(book.id);
                    done();
                  });
                });
      
            });
        });
       /*
        * Test the /PUT/:id route
        */
        describe('/PUT/:id book', () => {
            it('it should UPDATE a book given the id', (done) => {
                let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
                book.save((err, book) => {
                      chai.request(server)
                      .put('/book/' + book.id)
                      .send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778})
                      .end((err, res) => {
                            res.should.have.status(200);
                            res.body.should.be.a('object');
                            res.body.should.have.property('message').eql('Book updated!');
                            res.body.book.should.have.property('year').eql(1950);
                        done();
                      });
                });
            });
        });
      });
      
      

      We wanna make sure the message is the correct Book updated! one and that the year field was actually updated. Here is the output:

      Good, we are close to the end, we still gotta test the DELETE route.

      Test the /DELETE/:id Route

      The pattern is similar to the previous tests, we first store a book, delete it and test against the response. Copy and paste the following code:

      process.env.NODE_ENV = 'test';
      
      let mongoose = require("mongoose");
      let Book = require('../app/models/book');
      
      let chai = require('chai');
      let chaiHttp = require('chai-http');
      let server = require('../server');
      let should = chai.should();
      
      
      chai.use(chaiHttp);
      
      describe('Books', () => {
          beforeEach((done) => {
              Book.remove({}, (err) => { 
                 done();           
              });        
          });
        describe('/GET book', () => {
            it('it should GET all the books', (done) => {
                  chai.request(server)
                  .get('/book')
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('array');
                        res.body.length.should.be.eql(0);
                    done();
                  });
            });
        });
        describe('/POST book', () => {
            it('it should not POST a book without pages field', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('errors');
                        res.body.errors.should.have.property('pages');
                        res.body.errors.pages.should.have.property('kind').eql('required');
                    done();
                  });
            });
            it('it should POST a book ', (done) => {
                let book = {
                    title: "The Lord of the Rings",
                    author: "J.R.R. Tolkien",
                    year: 1954,
                    pages: 1170
                }
                  chai.request(server)
                  .post('/book')
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('message').eql('Book successfully added!');
                        res.body.book.should.have.property('title');
                        res.body.book.should.have.property('author');
                        res.body.book.should.have.property('pages');
                        res.body.book.should.have.property('year');
                    done();
                  });
            });
        });
        describe('/GET/:id book', () => {
            it('it should GET a book by the given id', (done) => {
                let book = new Book({ title: "The Lord of the Rings", author: "J.R.R. Tolkien", year: 1954, pages: 1170 });
                book.save((err, book) => {
                    chai.request(server)
                  .get('/book/' + book.id)
                  .send(book)
                  .end((err, res) => {
                        res.should.have.status(200);
                        res.body.should.be.a('object');
                        res.body.should.have.property('title');
                        res.body.should.have.property('author');
                        res.body.should.have.property('pages');
                        res.body.should.have.property('year');
                        res.body.should.have.property('_id').eql(book.id);
                    done();
                  });
                });
      
            });
        });
        describe('/PUT/:id book', () => {
            it('it should UPDATE a book given the id', (done) => {
                let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
                book.save((err, book) => {
                      chai.request(server)
                      .put('/book/' + book.id)
                      .send({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1950, pages: 778})
                      .end((err, res) => {
                            res.should.have.status(200);
                            res.body.should.be.a('object');
                            res.body.should.have.property('message').eql('Book updated!');
                            res.body.book.should.have.property('year').eql(1950);
                        done();
                      });
                });
            });
        });
       /*
        * Test the /DELETE/:id route
        */
        describe('/DELETE/:id book', () => {
            it('it should DELETE a book given the id', (done) => {
                let book = new Book({title: "The Chronicles of Narnia", author: "C.S. Lewis", year: 1948, pages: 778})
                book.save((err, book) => {
                      chai.request(server)
                      .delete('/book/' + book.id)
                      .end((err, res) => {
                            res.should.have.status(200);
                            res.body.should.be.a('object');
                            res.body.should.have.property('message').eql('Book successfully deleted!');
                            res.body.result.should.have.property('ok').eql(1);
                            res.body.result.should.have.property('n').eql(1);
                        done();
                      });
                });
            });
        });
      });
      
      

      Again the server returns a message and properties from mongoose that we assert so let’s check the output:

      Great, our tests are all positive and we have a good basis to continue testing our routes with more sophisticated assertions.

      Congratulation for completing the tutorial!

      Conclusion

      In this tutorial we faced the problem of testing our routes to provide our users a stable experience.

      We went through all the steps of creating a RESTful API, doing a naive test with POSTMAN and then propose a better way to test, in fact the main topic of the tutorial.

      It is good habit to always spend some time making tests to assure a server as reliable as possible but unfortunately it is often underestimated.

      During the tutorial we also discuss a few benefits of code testing and this will open doors to more advanced topics such as Test Driven Development (TDD).



      Source link

      Интеграция MongoDB с вашим приложением Node


      Введение

      При работе с Node.js вы можете столкнуться с ситуацией, когда вы разрабатываете проект, который будет сохранять и запрашивать данные. В данном случае вам нужно будет выбрать решение для базы данных, которое будет отвечать характеристикам данных приложения и типов запросов.

      В этом обучающем руководстве вы будете интегрировать базу данных MongoDB с существующим приложением Node. NoSQL базы данных, такие как MongoDB, могут быть полезными, если список ваших требований для данных включают масштабируемость и гибкость. MongoDB отлично интегрируется с Node, поскольку она предназначена для асинхронной работы с объектами JSON.

      Для интеграции MongoDB с вашим проектом вы будете использовать Object Document Mapper (ODM) Mongoose для создания схем и моделей данных вашего приложения. Это позволит вам организовать код приложения в соответствии с архитектурным шаблоном модель-представление-контроллер (MVC), который позволяет отделить логику обработки вводимых пользователей данных и логику структурирования данных и их отображения для пользователя. Использование такого шаблона может упрощать будущие тестирование и разработку, реализуя разделение проблем в базе кода.

      После прохождения руководства у вас будет рабочее приложение с информацией об акулах, которое будет принимать вводимые пользователем данные об их любимых акулах и отображать результаты в браузере:

      Вывод акулы

      Предварительные требования

      Шаг 1 — Создание пользователя Mongo

      Прежде чем мы начнем работу с кодом приложения, нам нужно будет создать административного пользователя, который будет иметь доступ к базе данных приложения. Этот пользователь будет иметь административные права в любой базе данных, что предоставит вам необходимую гибкость при переключении и создании новых баз данных при необходимости.

      Во-первых, проверьте, что MongoDB запущена на вашем сервере:

      • sudo systemctl status mongodb

      Следующий вывод показывает, что MongoDB запущена:

      Output

      ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2019-01-31 21:07:25 UTC; 21min ago ...

      Далее откройте командную строку Mongo для создания вашего пользователя:

      После этого вы будете должны попасть в командную строку администратора:

      Output

      MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.6.3 ... >

      При открытии командной строки вы увидите ряд административных предупреждений о том, что вам предоставляется неограниченный доступ к базе данных admin. Дополнительную информацию об ограничении такого доступа см. в руководстве «Установка и обеспечение безопасности MongoDB на Ubuntu 16.04», которая будет полезна вам при переходе к финальной настройке.

      А сейчас вы можете воспользоваться своим доступом к базе данных admin для создания пользователя с привилегиями userAdminAnyDatabase, которые позволят получать защищенный паролем доступ к базам данных вашего приложения.

      в командной строке укажите, что вы хотите использовать базу данных admin для создания вашего пользователя:

      Далее создайте роль и пароль, добавив имя пользователя и пароль с помощью команды db.createUser. Когда вы введете эту команду, командная строка будет добавлять три точки перед каждой строкой, пока работа команды не будет завершена. Обязательно замените пользователя и пароль, предоставленные здесь, на ваши имя пользователя и пароль:

      • db.createUser(
      • {
      • user: "sammy",
      • pwd: "your_password",
      • roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
      • }
      • )

      В результате этих действий будет создана запись для пользователя sammy в базе данных admin. Имя пользователя, которое вы выбрали, и база данных admin будут служить идентификаторами для вашего пользователя.

      Вывод для всего процесса будет выглядеть следующим образом, включая сообщение о том, что ввод был выполнен успешно:

      Output

      > db.createUser( ... { ... user: "sammy", ... pwd: "your_password", ... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] ... } ...) Successfully added user: { "user" : "sammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }

      После создания вашего пользователя и пароля вы можете закрыть командную строку Mongo:

      Теперь, когда вы создали пользователя для базы данных, вы можете перейти к клонированию кода начального проекта и добавлению библиотеки Mongoose, которая позволит вам реализовать схемы и модели для коллекций в ваших базах данных.

      Шаг 2 — Добавление информации о Mongoose и базе данных в проект

      Нашими следующими действия будут клонирование начального кода приложения и добавление в проект информации о Mongoose и нашей базе данных MongoDB.

      В корневой директории пользователя без прав root клонируйте репозиторий nodejs-image-demo из учетной записи DigitalOcean на GitHub. Этот репозиторий содержит код настройки, описанной в руководстве Сборка приложения Node.js с помощью Docker.

      Клонируйте репозиторий в директорию с именем node_project:

      • git clone https://github.com/do-community/nodejs-image-demo.git node_project

      Перейдите в директорию node_project:

      Прежде чем изменить код проекта, давайте посмотрим на структуру проекта с помощью команды tree.

      Подсказка: tree — это полезная команда для просмотра структур файлов и каталогов из командной строки. Для установки вы можете использовать следующую команду:

      Для ее использования перейдите в заданную директорию с помощью команды cd и введите tree. Также вы можете указать путь для начальной точки с помощью команды:

      • tree /home/sammy/sammys-project

      Введите следующее для просмотра директории node_project:

      Структура текущего проекта выглядит следующим образом:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html

      Мы будем добавлять директории в этот проект по мере прохождения руководства, и команда tree будет полезна для оценки нашего прогресса.

      Далее добавьте в проект пакет mongoose npm с помощью команды npm install:

      Эта команда создаст директорию node_modules в директории вашего проекта, используя зависимости, перечисленные в файле package.json проекта, и добавит mongoose в эту директорию. Также она добавит mongoose в зависимости, перечисленные в файле package.json. Более подробную информацию о файле package.json см. в шаге 1 руководства по сборке приложения Node.js с помощью Docker.

      Перед созданием любых схем или моделей мы добавим информацию о подключении базы данных, чтобы наше приложение смогло подключиться к нашей базе данных.

      Чтобы максимально разделить интересы вашего приложения, создайте отдельный файл для информации о подключении базы данных с именем db.js. Откройте файл в nano или вашем любимом редакторе:

      Во-первых, импортируйте модуль mongoose с помощью функции require:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      

      Это позволит вам получить доступ к встроенным методам Mongoose, которые вы будете использовать для создания подключения к вашей базе данных.

      Далее добавьте следующие константы для определения информации для URI подключения к Mongo. Хотя использование имени пользователя и пароля не является обязательным, мы добавим их, чтобы с их помощью можно было запрашивать аутентификацию для нашей базы данных. Обязательно замените имя пользователя и пароль, представленные ниже, на ваши собственные. Также вы можете назвать базу данных как-то иначе, чем «^>sharkinfo<^>, если захотите:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      
      const MONGO_USERNAME = 'sammy';
      const MONGO_PASSWORD = 'your_password';
      const MONGO_HOSTNAME = '127.0.0.1';
      const MONGO_PORT = '27017';
      const MONGO_DB = 'sharkinfo';
      

      Поскольку мы запускаем нашу базу данных локально, то в качестве имени хоста будем использовать 127.0.0.1. Это могло быть иначе в другом контексте разработки: например, если бы вы использовали отдельный сервер базы данных или работали с несколькими узлами в контейнеризированном рабочем процессе.

      Наконец, необходимо определить константу для URI и создать соединение с помощью метода mongoose.connect():

      ~/node_project/db.js

      ...
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, {useNewUrlParser: true});
      

      Обратите внимание, что в URI мы указали значение authSource для нашего пользователя в качестве базы данных admin. Это необходимо, поскольку мы указали имя пользователя в нашей строке подключения. С помощью флага useNewUrlParser и mongoose.connect() мы указали, что хотим использовать новый парсер URL-адресов Mongo.

      Сохраните и закройте файл после завершения редактирования.

      В качестве заключительного шага добавьте информацию о подключении базы данных в файл app.js, чтобы приложение могло использовать ее. Откройте app.js:

      Первая строка скрипта будет выглядеть следующим образом:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      
      const path = __dirname + '/views/';
      ...
      

      Под определением константы router, расположенным в верхней части файла, добавьте следующую строку:

      ~/node_project/app.js

      ...
      const router = express.Router();
      const db = require('./db');
      
      const path = __dirname + '/views/';
      ...
      

      С помощью этого мы укажем приложению на необходимость использования информации о подключении базы данных, указанной в файле db.js.

      Сохраните и закройте файл после завершения редактирования.

      После добавления информации о базе данных и Mongoose в ваш проект вы можете настроить схемы и модели, которые будут определять форму данных в вашей коллекции sharks.

      Шаг 3 — Создание схем и моделей Mongoose

      Нашим следующим шагом будет изучение структуры коллекции sharks, которую будут создавать пользователи в базе данных sharkinfo с помощью ввода данных. Какую структуру должны иметь создаваемые нами документы? Страница информации об акуле в нашем текущем приложении содержит некоторые сведения о разных акулах и их поведении:

      Страница информации об акулах

      В соответствии с этой темой мы можем попросить пользователей добавлять новых акул с общими данными о них. Эта цель будет определять то, как мы создадим нашу схему.

      Чтобы обеспечить отличие ваших схем и моделей от других частей приложения, создайте директорию models в текущей директории проекта:

      Далее откройте файл sharks.js для создания вашей схемы и модели:

      Импортируйте модуль mongoose в верхней части файла:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      

      Ниже определите объект Schema для использования в качестве основы для вашей схемы акулы:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      const Schema = mongoose.Schema;
      

      Теперь вы можете определить поля, которые хотите включить в свою схему. Поскольку мы хотим создать коллекцию с отдельными акулами и информацией об их поведении, давайте добавим ключ name и ключ character. Добавьте следующую схему Shark под определениями ваших констант:

      ~/node_project/models/sharks.js

      ...
      const Shark = new Schema ({
              name: { type: String, required: true },
              character: { type: String, required: true },
      });
      

      Это определение включает информацию о том, какой тип ввода мы ожидаем от пользователей, в данном случае string, а также о том, обязательно ли вводить эти данные.

      Наконец, создайте модель Shark с помощью функции model() в Mongoose. Эта модель позволит запрашивать документы из вашей коллекции и выполнять валидацию новых документов. Добавьте следующую строку внизу файла:

      ~/node_project/models/sharks.js

      ...
      module.exports = mongoose.model('Shark', Shark)
      

      Эта последняя строка делает нашу модель Shark доступной для использования в качестве модуля с помощью свойства module.exports. Это свойство определяет значения, которые модуль будет экспортировать, делая их доступными для использования в любом месте приложения.

      Итоговый файл models/sharks.js выглядит следующим образом:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      const Schema = mongoose.Schema;
      
      const Shark = new Schema ({
              name: { type: String, required: true },
              character: { type: String, required: true },
      });
      
      module.exports = mongoose.model('Shark', Shark)
      

      Сохраните и закройте файл после завершения редактирования.

      После настройки схемы Shark и модели вы можете начать работу над логикой, которая будет определять, как приложение будет обрабатывать вводимые данные пользователя.

      Шаг 4 — Создание контроллеров

      На следующем шаге мы сможем создать компонент контроллера, который будет определять, как вводимые пользователем данные будут сохраняться в нашей базе данных и извлекаться из нее.

      Во-первых, создайте директорию для контроллера:

      Далее откройте в этой папке файл sharks.js:

      • nano controllers/sharks.js

      Сверху файла мы импортируем модуль в нашу модель Shark, чтобы мы могли использовать его в логике нашего контроллера. Также мы импортируем модуль path для доступа к утилитам, которые позволят нам задать путь к форме, куда пользователи будут вводить данные об акулах.

      Добавьте следующие обязательные функции в начало файла:

      ~/node_project/controllers/sharks.js

      const path = require('path');
      const Shark = require('../models/sharks');
      

      Далее мы напишем последовательность функций, которые мы будем экспортировать в модуль контроллера с помощью сочетания клавиш exports. Эти функции будут включать три задачи, связанные с данными об акулах нашего пользователя:

      • Отправка пользователям формы ввода акулы.
      • Создание новой записи акулы.
      • Отображение акул для пользователей.

      Для начала создайте функцию index для отображения страницы с акулами с формой ввода. Добавьте эту функцию под импортами:

      ~/node_project/controllers/sharks.js

      ...
      exports.index = function (req, res) {
          res.sendFile(path.resolve('views/sharks.html'));
      };
      

      Затем под функцией index добавьте функцию с именем create для создания новой записи акулы в коллекции sharks:

      ~/node_project/controllers/sharks.js

      ...
      exports.create = function (req, res) {
          var newShark = new Shark(req.body);
          console.log(req.body);
          newShark.save(function (err) {
                  if(err) {
                  res.status(400).send('Unable to save shark to database');
              } else {
                  res.redirect('/sharks/getshark');
              }
        });
                     };
      

      Эта функция будет вызываться, когда пользователь публикует данные об акуле в форме на странице sharks.html. Мы создадим маршрут с конечной точкой POST позже в этом руководстве, когда мы будем создавать маршруты нашего приложения. Используя body запроса POST, наша функция create будет создавать новый объект документа акулы, который называется здесь newShark с помощью модели Shark, которую мы импортировали. Мы добавили метод console.log для вывода записи акулы в консоль, чтобы убедиться, что наш метод POST работает надлежащим образом, но вы можете пропустить этот шаг, если хотите.

      Используя объект newShark, функция create будет вызывать метод model.save() Mongoose для создания нового документа акулы, используя ключ, который вы определили в модели Shark. Эта функция обратного вызова соответствует стандартной схеме обратного вызова Node: callback(error, results). В случае ошибки мы будем отправлять сообщение об ошибке нашим пользователям, а в случае успеха мы будем использовать метод res.redirect() для отправки пользователей в конечную точку, которая будет возвращать им информацию об акуле в браузере.

      Наконец, функция list будет отображать содержимое коллекции для пользователя. Добавьте следующий код под функцией create:

      ~/node_project/controllers/sharks.js

      ...
      exports.list = function (req, res) {
              Shark.find({}).exec(function (err, sharks) {
                      if (err) {
                              return res.send(500, err);
                      }
                      res.render('getshark', {
                              sharks: sharks
                   });
              });
      };
      

      Эта функция использует модель Shark с методом model.find() Mongoose для возврата акул, которые были добавлены в коллекцию sharks. Она выполняет эту задачу, возвращая объект запроса — в данном случае это все записи в коллекции sharks — в качестве обеспечения, используя функцию exec() Mongoose. В случае ошибки функция обратного вызова будет отправлять ошибку с кодом 500.

      Возвращаемый объект запроса с коллекцией sharks будет отображаться на странице getshark, которую мы создадим на следующем шаге с помощью языка шаблонов EJS.

      Итоговый файл будет выглядеть примерно так:

      ~/node_project/controllers/sharks.js

      const path = require('path');
      const Shark = require('../models/sharks');
      
      exports.index = function (req, res) {
          res.sendFile(path.resolve('views/sharks.html'));
      };
      
      exports.create = function (req, res) {
          var newShark = new Shark(req.body);
          console.log(req.body);
          newShark.save(function (err) {
                  if(err) {
                  res.status(400).send('Unable to save shark to database');
              } else {
                  res.redirect('/sharks/getshark');
              }
        });
                     };
      
      exports.list = function (req, res) {
              Shark.find({}).exec(function (err, sharks) {
                      if (err) {
                              return res.send(500, err);
                      }
                      res.render('getshark', {
                              sharks: sharks
                   });
              });
      };
      

      Обязательно учитывайте, что мы не будем использовать здесь стрелочные функции, вы можете включить их в этот код, если будете использовать этот код в собственной разработке.

      Сохраните и закройте файл после завершения редактирования.

      Перед переходом к следующему шагу вы снова можете запустить tree из директории node_project для просмотра структуры проекта на данный момент. На этот раз для лаконичности мы будем использовать tree, чтобы пропустить node_modules, используя параметр -I:

      После внесения этих изменений ваш проект будет выглядеть следующим образом:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html

      Теперь, когда у вас есть компонент контроллера для управления тем, как пользовательский ввод будет сохраняться и передаваться пользователю, вы можете перейти к созданию представлений, которые будут реализовывать логику вашего контроллера.

      Шаг 5 — Использование EJS и межплатформенное ПО Express для сбора и отображения данных

      Чтобы наше приложение могло работать с данными пользователя, нам нужно сделать две вещи: сначала мы добавим встроенную функцию межплатформенного ПО Express, urlencoded(), которая позволит нашему приложению парсить вводимые пользователем данные. А затем мы добавим теги шаблона в наши представления, чтобы активировать динамическое взаимодействие с данными пользователя в нашем коде.

      Для работы с функцией urlencoded() Express откройте ваш файл app.js:

      Над функцией express.static() добавьте следующую строку:

      ~/node_project/app.js

      ...
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      ...
      

      Добавление этой функции позволит получить доступ к распарсенным данным POST из нашей формы информации об акуле. Мы укажем значение true параметра extended для обеспечения большей гибкости в отношении типа данных, который будет парсить ваше приложение (включая такие вещи как вложенные объекты). Дополнительную информацию о параметрах см. в документации для функции.

      Сохраните и закройте файл после завершения редактирования.

      Далее мы добавим функциональность шаблона для наших представлений. Во-первых, установите пакет ejs с помощью команды npm install:

      Далее откройте файл sharks.html в папке views:

      На шаге 3 мы просматривали эту страницу для определения того, как мы должны записать нашу схему и модель Mongoose:

      Страница информации об акулах

      Теперь, вместо того, чтобы использовать дизайн с двумя столбцами, мы добавим третий столбец с формой, где пользователи смогут вводить информацию об акулах.

      В качестве первого шага укажите размеры 4 для существующих столбцов, чтобы создать три столбца одинакового размера. Обратите внимание, что вам нужно внести эти изменения в двух строках, которые сейчас выглядят следующим образом: <div class="col-lg-6">. Укажите значения <div class="col-lg-4">:

      ~/node_project/views/sharks.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          </div>
        </div>
      
       </html>
      

      Чтобы познакомиться с системой Bootstrap, включая схемы столбцов и рядов, см. Введение в Bootstrap.

      Далее добавьте другой столбец, который включает конечную точку для запроса POST с пользовательскими данными об акуле и тегами шаблона EJS, который будут получать эти данные. Этот столбец будет располагаться под закрывающими тегами </p> и </div> предыдущего столбца и над закрывающими тегами для ряда, контейнера и HTML документа. Эти закрывающие теги уже есть в коде; они также обозначены ниже комментариями. Оставьте их на месте, когда вы добавите следующий код для создания нового столбца:

      ~/node_project/views/sharks.html

      ...
             </p> <!-- closing p from previous column -->
         </div> <!-- closing div from previous column -->
      <div class="col-lg-4">
                  <p>
                      <form action="/sharks/addshark" method="post">
                          <div class="caption">Enter Your Shark</div>
                          <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                          <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                          <button type="submit">Submit</button>
                      </form>
                  </p>
              </div>
          </div> <!-- closing div for row -->
      </div> <!-- closing div for container -->
      
      </html> <!-- closing html tag -->
      

      В теге form вы добавите конечную точку "/sharks/addshark" для пользовательских данных об акуле и укажете метод POST для их передачи. В полях ввода вы задали поля для параметров «Shark name» и «Shark Character» в соответствие с определенной ранее моделью «Shark».

      Чтобы добавить пользовательских ввод в коллекцию sharks, вы используете теги шаблона EJS (<%=,%>) вместе с синтаксисом JavaScript для разметки пользовательских записей в соответствующие поля во вновь созданном документе. Дополнительные данные об объектах JavaScript см. в статье Знакомство с объектами JavaScript. Дополнительные данные о тегах шаблона EJS см. в документации EJS.

      Весь контейнер со всеми тремя столбцами, включая столбец с формой ввода акулы, будет выглядеть следующим образом после завершения этих действий:

      ~/node_project/views/sharks.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          <div class="col-lg-4">
                  <p>
                      <form action="/sharks/addshark" method="post">
                          <div class="caption">Enter Your Shark</div>
                          <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                          <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                          <button type="submit">Submit</button>
                      </form>
                  </p>
              </div>
          </div>
        </div>
      
      </html>
      

      Сохраните и закройте файл после завершения редактирования.

      Теперь, когда у вас есть возможность собирать пользовательские данные, вы можете создать конечную точку для отображения возвращаемых акул и связанную с ними информацию.

      Скопируйте модифицированный файл sharks.html в файл с именем getshark.html:

      • cp views/sharks.html views/getshark.html

      Откройте getshark.html:

      Внутри файла мы изменим столбец, который мы использовали для создания нашей формы ввода акул, заменив его на столбец, который будет отображать акул в нашей коллекции sharks. Ваш код будет размещаться между существующими тегами </p> и </div> из предыдущего столбца и закрывающими тегами для ряда, контейнера и HTML документа. Обязательно оставьте эти теги на месте, когда вы будете добавлять следующий код для создания столбца:

      ~/node_project/views/getshark.html

      ...
             </p> <!-- closing p from previous column -->
         </div> <!-- closing div from previous column -->
      <div class="col-lg-4">
                 <p>
                    <div class="caption">Your Sharks</div>
                        <ul>
                           <% sharks.forEach(function(shark) { %>
                              <p>Name: <%= shark.name %></p>
                              <p>Character: <%= shark.character %></p>
                           <% }); %>
                        </ul>
                  </p>
              </div>
          </div> <!-- closing div for row -->
      </div> <!-- closing div for container -->
      
      </html> <!-- closing html tag -->
      

      Здесь вы используете теги шаблона EJS и метод forEach() для вывода каждого значения в коллекции sharks, включая информацию о последней добавленной акуле.

      Весь контейнер со всеми тремя столбцами, включая столбец с коллекцией sharks, будет выглядеть следующим образом после завершения этих действий:

      ~/node_project/views/getshark.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          <div class="col-lg-4">
                  <p>
                    <div class="caption">Your Sharks</div>
                        <ul>
                           <% sharks.forEach(function(shark) { %>
                              <p>Name: <%= shark.name %></p>
                              <p>Character: <%= shark.character %></p>
                           <% }); %>
                        </ul>
                  </p>
              </div>
          </div>
        </div>
      
      </html>
      

      Сохраните и закройте файл после завершения редактирования.

      Чтобы приложение могло использовать созданные вами шаблоны, вам нужно будет добавить несколько строк в файл app.js. Откройте его снова:

      Над местом, куда вы добавили функцию express.urlencoded(), добавьте следующие строки:

      ~/node_project/app.js

      ...
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      
      ...
      

      Метод app.engine указывает приложению на необходимость преобразования движка шаблона EJS в HTML файлы, а app.set определяет движок представления по умолчанию.

      Теперь ваш файл app.js будет выглядеть следующим образом:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      const db = require('./db');
      
      const path = __dirname + '/views/';
      const port = 8080;
      
      router.use(function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      
      router.get('/',function(req,res){
        res.sendFile(path + 'index.html');
      });
      
      router.get('/sharks',function(req,res){
        res.sendFile(path + 'sharks.html');
      });
      
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      app.use('/', router);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Теперь, когда вы создали представления, которые могут динамически работать с данными пользователя, пришло время создать маршруты вашего проекта для объединения представлений и логики контроллера.

      Шаг 6 — Создание маршрутов

      Завершающим шагом сборки всех компонентов приложения будет создание маршрутов. Мы разделим наши маршруты согласно функциям, включая маршрут к начальной странице нашего приложения и другой маршрут к нашей странице с акулами. Наш маршрут sharks будет находиться там, где мы будем интегрировать логику нашего контроллера с представлениями, которые мы создали на предыдущем шаге.

      Во-первых, создайте директорию routes:

      Далее откройте в этой директории файл с именем index.js:

      Этот файл будет импортировать объекты express, router и path, позволяющие нам определить маршруты, которые мы хотим экспортировать с объектом router, и делая возможной динамическую работу с путем файла. Добавьте следующую строку вверху файла:

      ~/node_project/routes/index.js

      const express = require('express');
      const router = express.Router();
      const path = require('path');
      

      Далее добавьте следующую функцию router.use, которая загружает промежуточную функцию, которая будет регистрировать запросы маршрутизатора и передавать их на маршрут приложения:

      ~/node_project/routes/index.js

      ...
      
      router.use (function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      

      Запросы к корню нашего приложения будут направляться сначала сюда, а уже отсюда пользователи будут направляться на начальную страницу нашего приложения согласно маршруту, который мы определим здесь. Добавьте следующий код под функцией router.use для определения маршрута к начальной странице:

      ~/node_project/routes/index.js

      ...
      
      router.get('/',function(req,res){
        res.sendFile(path.resolve('views/index.html'));
      });
      

      Когда пользователи будут посещать наше приложение, первым местом, куда мы будем направлять их, будет начальная страница index.html, которую мы разместили в директории views.

      Наконец, чтобы сделать эти маршруты доступными в качестве импортируемого модуля в любом месте приложения, добавьте закрывающее выражение в конце файла для экспорта объекта router:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      Итоговый файл будет выглядеть примерно так:

      ~/node_project/routes/index.js

      const express = require('express');
      const router = express.Router();
      const path = require('path');
      
      router.use (function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      
      router.get('/',function(req,res){
        res.sendFile(path.resolve('views/index.html'));
      });
      
      module.exports = router;
      

      Сохраните и закройте файл после завершения редактирования.

      Далее откройте файл sharks.js для определения того, как приложение будет использовать различные конечные точки и представления, которые мы создали для работы с пользовательским вводом данных об акулах:

      Вверху файла импортируйте объекты express и router:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      

      Далее импортируйте модуль с именем sharks, который позволит нам работать с экспортируемыми функциями, определенными с помощью контроллера:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      const shark = require('../controllers/sharks');
      

      Теперь вы можете создавать маршруты с помощью функций index, create и list, определенных в файле контроллера sharks. Каждый маршрут будет ассоциироваться с соответствующим методом HTTP: GET в случае отображения главной страницы с информацией об акулах и возврата списка акул пользователю, и POST в случае создания новой записи акулы:

      ~/node_project/routes/sharks.js

      ...
      
      router.get('/', function(req, res){
          shark.index(req,res);
      });
      
      router.post('/addshark', function(req, res) {
          shark.create(req,res);
      });
      
      router.get('/getshark', function(req, res) {
          shark.list(req,res);
      });
      

      Каждый маршрут использует соответствующую функцию в controllers/sharks.js, поскольку мы сделали этот модуль доступным, импортировав его в верхней части этого файла.

      После этого закройте файл, привязав эти маршруты к объекту router и выполнив их экспорт:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      Итоговый файл будет выглядеть примерно так:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      const shark = require('../controllers/sharks');
      
      router.get('/', function(req, res){
          shark.index(req,res);
      });
      
      router.post('/addshark', function(req, res) {
          shark.create(req,res);
      });
      
      router.get('/getshark', function(req, res) {
          shark.list(req,res);
      });
      
      module.exports = router;
      

      Сохраните и закройте файл после завершения редактирования.

      Последний шаг на пути к предоставлению доступа к этим маршрутам для вашего приложения будет состоять в их добавлении в app.js. Откройте этот файл снова:

      Под вашей константой db добавьте следующий импорт для ваших маршрутов:

      ~/node_project/app.js

      ...
      const db = require('./db');
      const sharks = require('./routes/sharks');
      

      Далее замените функцию app.use, которая в настоящее время монтирует ваш объект router, на следующую строку, которая будет монтировать модуль маршрута sharks:

      ~/node_project/app.js

      ...
      app.use(express.static(path));
      app.use('/sharks', sharks);
      
      app.listen(port, function () {
              console.log("Example app listening on port 8080!")
      })
      

      Теперь вы можете удалить маршруты, которые были определены в этом файле ранее, поскольку вы импортируете маршруты вашего приложения с помощью модуля маршрутизации sharks.

      Окончательная версия файла app.js будет выглядеть следующим образом:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      const db = require('./db');
      const sharks = require('./routes/sharks');
      
      const path = __dirname + '/views/';
      const port = 8080;
      
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      app.use('/sharks', sharks);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Сохраните и закройте файл после завершения редактирования.

      Теперь вы можете снова запустить tree для просмотра окончательной структуры вашего проекта:

      Теперь структура проекта будет выглядеть следующим образом:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json ├── routes │ ├── index.js │ └── sharks.js └── views ├── css │ └── styles.css ├── getshark.html ├── index.html └── sharks.html

      После создания и добавления всех компонентов вашего приложения вы можете добавить тестовую акулу в базу данных!

      Если вы следовали указаниям руководства по начальной настройке сервера из предварительных требований, вам нужно будет изменить ваш брандмауэр, поскольку в настоящее время он пропускает только трафик SSH. Чтобы разрешить трафик на порт 8080, выполните команду:

      Запустите приложение:

      Откройте в браузере адрес http://your_server_ip:8080. Вы увидите следующую начальную страницу:

      Начальная страница приложения

      Нажмите кнопку Get Shark Info (Получить информацию об акулах). Вы увидите следующую информационную страницу с добавленной формой ввода акулы:

      Форма информации об акуле

      В этой форме добавьте акулу по вашему выбору. В демонстрационных целях мы добавим Megalodon Shark в поле Shark Name (Имя акулы) и Ancient в поле Shark Character (Тип акулы):

      Заполненная форма акулы

      Нажмите кнопку Submit (Отправить). Вы увидите страницу с информацией об акуле, отображаемая для вас:

      Вывод акулы

      Также вы увидите вывод в консоли, указывающий, что акула добавлена в вашу коллекцию:

      Output

      Example app listening on port 8080! { name: 'Megalodon Shark', character: 'Ancient' }

      Если вы хотите создать новую запись акулы, вернитесь на страницу Sharks и повторите процесс добавления акулы.

      Теперь у вас есть рабочее приложение с информацией об акулах, позволяющее пользователям добавить информацию о своих любимых акулах.

      Заключение

      В этом обучающем руководстве вы создали приложение Node, выполнив интеграцию с базой данных MongoDB и переписав логику приложения с помощью шаблона архитектуры MVC. Это приложение может выступать в качестве хорошей начальной точки для полномасштабного CRUD приложения.

      Дополнительную информацию по использованию шаблонов MVC в другом контексте см. в нашей серии о разработке Django или руководстве Создание современного веб-приложения для управления данными клиента с помощью Django и React в Ubuntu 18.04.

      Дополнительную информацию о работе с MongoDB см. в нашей библиотеке обучающих материалов для MongoDB.



      Source link

      Cómo instalar MongoDB con su aplicación de Node


      Introducción

      Al trabajar con Node.js, es posible que se encuentre desarrollando un proyecto que almacene y consulte datos. En este caso, deberá elegir una solución de base de datos que tenga sentido para los datos y los tipos de consulta de su aplicación.

      A través de este tutorial, integrará una base de datos de MongoDB con una aplicación de Node existente. Las bases de datos de NoSQL como MongoDB pueden ser útiles si entre los requisitos de sus datos se incluyen la escalabilidad y la flexibilidad. MongoDB también se integra bien con Node, ya que está diseñado para funcionar de forma asíncrona con objetos JSON.

      Para integrar MongoDB en su proyecto utilizará Mongoose, el asignador de documento objeto (ODM), a fin de crear esquemas y modelos para los datos de su aplicación. Esto le permitirá organizar el código de su aplicación siguiendo el patrón de arquitectura de modelo-vista-controlador (MVC), lo cual le permitirá separar la lógica de cómo su aplicación gestiona la entradas del usuario de la forma en que sus datos se estructuran y se muestran al usuario. El uso de este patrón puede facilitar las pruebas y el desarrollo futuro introduciendo una separación de problemas en su base de código.

      Al completar el tutorial, contará con una aplicación de información sobre tiburones que funcionará y recopilará entradas del usuario sobre sus tiburones favoritos y mostrará los resultados en el navegador:

      Resultados de tiburones

      Requisitos previos

      Paso 1: Crear un usuario de Mongo

      Antes de comenzar a trabajar con el código de la aplicación, crearemos un usuario administrativo que tendrá acceso a la base de datos de nuestra aplicación. Este usuario tendrá privilegios administrativos en cualquier base de datos, lo que le brindará la flexibilidad para cambiar y crear nuevas bases de datos según sea necesario.

      Primero, compruebe que MongoDB esté en ejecución en su servidor:

      • sudo systemctl status mongodb

      El siguiente resultado indica que MongoDB se encuentra en ejecución:

      Output

      ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Thu 2019-01-31 21:07:25 UTC; 21min ago ...

      A continuación, abra el shell de Mongo para crear su usuario:

      Esto lo situará en un shell administrativo:

      Output

      MongoDB shell version v3.6.3 connecting to: mongodb://127.0.0.1:27017 MongoDB server version: 3.6.3 ... >

      Verá algunas advertencias administrativas cuando abra el shell debido a su acceso sin restricciones a la base de datos de admin. Puede obtener más información sobre la restricción de este acceso leyendo Cómo instalar y proteger MongoDB en Ubuntu 16.04, para cuando realice una transición a una configuración de producción.

      Por ahora, puede usar su acceso a la base de datos de admin para crear un usuario con privilegios de userAdminAnyDatabase, lo cual permitirá el acceso protegido por contraseña a las bases de datos de su aplicación.

      En el shell, especifique que desea usar la base de datos de admin para crear su usuario:

      A continuación, cree un rol y una contraseña agregando un nombre de usuario y una contraseña con el comando db.createUser. Una vez que escriba este comando, el shell antepondrá tres puntos antes de cada línea hasta que el comando se complete. Asegúrese de sustituir el usuario y la contraseña que se proporcionan aquí por su propio nombre de usuario y su contraseña:

      • db.createUser(
      • {
      • user: "sammy",
      • pwd: "your_password",
      • roles: [ { role: "userAdminAnyDatabase", db: "admin" } ]
      • }
      • )

      Con esto se crea una entrada para el usuario sammy en la base de datos de admin. El nombre de usuario que seleccione y la base de datos de admin servirán como identificadores para su usuario.

      El resultado de todo el proceso tendrá el aspecto que se muestra a continuación. Se incluye el mensaje que indica que la entrada se realizó de manera correcta:

      Output

      > db.createUser( ... { ... user: "sammy", ... pwd: "your_password", ... roles: [ { role: "userAdminAnyDatabase", db: "admin" } ] ... } ...) Successfully added user: { "user" : "sammy", "roles" : [ { "role" : "userAdminAnyDatabase", "db" : "admin" } ] }

      Una vez creados su usuario y contraseña, podrá cerrar el shell de Mongo:

      Ahora que creó su usuario de base de datos, puede proceder con la clonación del código del proyecto de inicio y añadir la biblioteca de Mongoose, que le permitirá implementar esquemas y modelos para las colecciones de sus bases de datos.

      Paso 2: Agregar información sobre Mongoose y bases de datos al proyecto

      Nuestros próximos pasos serán clonar el código de inicio de la aplicación y añadir información sobre Mongoose y nuestra base de datos de MongoDB al proyecto.

      En el directorio principal de su usuario no root, clone el repositorio nodejs-image-demo de la cuenta de GitHub de la comunidad de DigitalOcean. Este repositorio incluye el código de la configuración descrita en Cómo crear una aplicación de Node.js con Docker.

      Clone el repositorio en un directorio llamado node_project:

      • git clone https://github.com/do-community/nodejs-image-demo.git node_project

      Diríjase al directorio <^>node_project<^>:

      Antes de modificar el código del proyecto, observaremos la estructura este último usando el comando tree.

      Sugerencia: tree es un comando útil que permite visualizar estructuras de archivos y directorios desde la línea de comandos. Puede instalarlo con el siguiente comando:

      Para emplearlo, use el comando cd para pasar a un directorio determinado y escriba tree. También puede proporcionar la ruta al punto de partida con un comando como el siguiente:

      • tree /home/sammy/sammys-project

      Escriba lo siguiente para ver el directorio node_project:

      La estructura del proyecto actual tiene el siguiente aspecto:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html

      Añadiremos directorios a este proyecto a medida que avancemos en el tutorial,y el comando tree será útil para ayudarnos a monitorear nuestro progreso.

      A continuación, añada el paquete de npm de moongoose al proyecto con el comando npm install:

      Con este comando se creará un directorio node_modules en el directorio de su proyecto, se usarán las dependencias enumeradas en el archivo package.json del proyecto y se agregará mongoose a ese directorio. También se agregará mongoose a las dependencias enumeradas en su archivo package.json. Para hallar un análisis más detallado sobre package.json, consulte el paso 1 de Cómo crear una aplicación de Node.js con Docker.

      Antes de crear cualquier esquema o modelo de Mongoose, añadiremos la información de conexión de nuestra base de datos para que nuestra aplicación pueda conectarse a nuestra base de datos.

      A fin de separar al máximo los aspectos de su aplicación que sean motivo de inquietud, cree un archivo por separado para la información de conexión de su base de datos llamada db.js. Puede abrir este archivo con nano o con su editor favorito:

      Primero, importe el módulo de mongoose usando la función require:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      

      Esto le brindará acceso a los métodos incorporados de Mongoose que usará para crear la conexión a su base de datos.

      A continuación, agregue las siguientes constantes a fin de definir información para la conexión de URI de Mongo. Aunque el nombre de usuario y la contraseña son opcionales, los incluiremos para poder exigir la autenticación para nuestra base de datos. Asegúrese de sustituir el nombre de usuario y la contraseña que se muestran a continuación por su propia información, y podrá dar a la base de datos un nombre que no sea 'sharkinfo' si así lo prefiere:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      
      const MONGO_USERNAME = 'sammy';
      const MONGO_PASSWORD = 'your_password';
      const MONGO_HOSTNAME = '127.0.0.1';
      const MONGO_PORT = '27017';
      const MONGO_DB = 'sharkinfo';
      

      Debido a que ejecutamos nuestra base de datos a nivel local, usamos 127.0.0.1 como nombre de host. Esto cambiará en otros contextos de desarrollo: por ejemplo, si utiliza un servidor de bases de datos independiente o si trabaja con varios nodos en un flujo de trabajo en contenedores.

      Por último, defina una constante para la URI y cree la conexión usando el método mongoose.connect():

      ~/node_project/db.js

      ...
      const url = `mongodb://${MONGO_USERNAME}:${MONGO_PASSWORD}@${MONGO_HOSTNAME}:${MONGO_PORT}/${MONGO_DB}?authSource=admin`;
      
      mongoose.connect(url, {useNewUrlParser: true});
      

      Tenga en cuenta que en la URI especificamos el authSource de nuestro usuario como base de datos de admin. Esto es necesario, ya que especificamos un nombre de usuario en nuestra cadena de conexión. El uso del indicador useNewUrlParser con mongoose.connect() especifica que deseamos usar el nuevo analizador de URL de Mongo.

      Guarde y cierre el archivo cuando concluya la edición.

      Como paso final, añada la información de conexión de la base de datos al archivo app.js para que la aplicación pueda utilizarla. Abra app.js:

      Las primeras líneas del archivo tendrán este aspecto:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      
      const path = __dirname + '/views/';
      ...
      

      Debajo de la definición de la constante router, situada cerca de la parte superior del archivo, agregue la siguiente línea:

      ~/node_project/app.js

      ...
      const router = express.Router();
      const db = require('./db');
      
      const path = __dirname + '/views/';
      ...
      

      Esto indica a la aplicación que utilice la información de conexión de la base de datos especificada en db.js.

      Guarde y cierre el archivo cuando concluya la edición.

      Una vez que se implemente la información de su base de datos y se añada Mongoose a su proyecto, estará listo para crear los esquemas y los modelos que configurarán los datos de su colección sharks.

      Paso 3: Crear esquemas y modelos de Mongoose

      Nuestro siguiente paso será pensar en la estructura de la colección sharks que los usuarios crearán en la base de datos sharkinfo con sus datos. ¿Qué estructura queremos que tengan estos documentos creados? En la página de información sobre tiburones de nuestra aplicación actual se incluyen algunos detalles sobre diferentes tiburones y sus comportamientos:

      Página de información sobre tiburones

      En línea con este tema, podemos disponer que los usuarios añadan nuevos tiburones con detalles sobre su carácter general. Este objetivo dará forma a la manera en que creemos nuestro esquema.

      Para mantener sus esquemas y modelos diferenciados de las demás partes de su aplicación, cree un directorio models en el directorio de proyectos actual:

      A continuación, abra un archivo llamado sharks.js para crear su esquema y modelo:

      Importe el módulo mongoose en la parte superior del archivo:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      

      Debajo de esto, defina un objeto de Schema que se utilice como base para su esquema de tiburones:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      const Schema = mongoose.Schema;
      

      Ahora podrá definir los campos que desee incluir en su esquema. Debido a que deseamos crear una colección con tiburones individuales e información sobre sus comportamientos, incluiremos una clave name y una clave character. Añada el siguiente esquema Shark debajo de sus definiciones de constantes:

      ~/node_project/models/sharks.js

      ...
      const Shark = new Schema ({
              name: { type: String, required: true },
              character: { type: String, required: true },
      });
      

      En esta definición se incluye información sobre el tipo de entrada que esperamos de los usuarios; en este caso, una secuencia de comandos y si es necesaria o no la entrada.

      Por último, cree el modelo Shark usando la función model() de Mongoose. Este modelo le permitirá consultar documentos de su colección y validar nuevos documentos. Añada la siguiente línea en la parte inferior del archivo:

      ~/node_project/models/sharks.js

      ...
      module.exports = mongoose.model('Shark', Shark)
      

      Esta última línea hace que nuestro modelo Shark esté disponible como un módulo usando la propiedad module.exports. Esta propiedad define los valores que el módulo exportará y los pondrá a disposición para su uso en cualquier lugar de la aplicación.

      El archivo completado de models/sharks.js tiene este aspecto:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      const Schema = mongoose.Schema;
      
      const Shark = new Schema ({
              name: { type: String, required: true },
              character: { type: String, required: true },
      });
      
      module.exports = mongoose.model('Shark', Shark)
      

      Guarde y cierre el archivo cuando concluya la edición.

      Una vez implementados el esquema y el modelo de Shark, puede comenzar a trabajar en la lógica que determinará la forma en que su aplicación manejará las entradas del usuario.

      Paso 4: Crear los controladores

      Nuestro siguiente paso será crear el componente del controlador que determinará la manera en que las entradas del usuario se guarden en nuestra base de datos y se devuelvan a este.

      Primero, cree un directorio para el controlador:

      A continuación, abra un archivo llamado sharks.js en esa carpeta:

      • nano controllers/sharks.js

      En la parte superior del archivo, importaremos el módulo con nuestro modelo Shark para poder utilizarlo en la lógica de nuestro controlador. También importaremos el módulo path para acceder a las utilidades que nos permitirán establecer la ruta en el formulario en el que los usuarios ingresarán sus tiburones.

      Añada las siguientes funciones require al inicio del archivo:

      ~/node_project/controllers/sharks.js

      const path = require('path');
      const Shark = require('../models/sharks');
      

      A continuación, escribiremos una secuencia de funciones que exportaremos con el módulo de controlador usando el acceso rápido exports de Node. Estas funciones incluirán las tres tareas relacionadas con los datos de tiburones de nuestro usuario:

      • Enviar a los usuarios el formulario de entrada de tiburones
      • Crear una nueva entrada sobre tiburones
      • Mostrar los tiburones de vuelta a los usuarios

      Para comenzar, cree una función index para mostrar la página de tiburones con el formulario de entrada. Añada esta función debajo de sus importaciones:

      ~/node_project/controllers/sharks.js

      ...
      exports.index = function (req, res) {
          res.sendFile(path.resolve('views/sharks.html'));
      };
      

      A continuación, debajo de la función index, agregue una función llamada create para crear una nueva entrada sobre tiburones en su colección sharks:

      ~/node_project/controllers/sharks.js

      ...
      exports.create = function (req, res) {
          var newShark = new Shark(req.body);
          console.log(req.body);
          newShark.save(function (err) {
                  if(err) {
                  res.status(400).send('Unable to save shark to database');
              } else {
                  res.redirect('/sharks/getshark');
              }
        });
                     };
      

      Esta función se mostrará cuando un usuario publique datos de tiburones en el formulario en la página sharks.html. Crearemos la ruta con este extremo de POST más adelante en el tutorial cuando creemos las rutas de nuestra aplicación. Con el body de la solicitud de POST, nuestra función create creará un nuevo objeto de documentos de tiburones, aquí llamado newShark, usando el modelo Shark que importamos. Hemos añadido un método de console.log a fin de mostrar la entrada sobre el tiburón en la consola para comprobar que nuestro método POST funciona como está previsto, pero puede optar por omitir esto si así lo prefiere.

      Mediante el objeto newShark, la función create llamará al método model.save() de Mongoose para crear un nuevo documento sobre tiburones usando las claves que definió en el modelo Shark. Esta función de devolución de llamada sigue el patrón estándar de devolución de llamada de Node: callback(error, results). Si se produce un error, enviaremos un mensaje que notificará el error a nuestros usuarios. Si la operación tiene éxito, utilizaremos el método res.redirect() para enviar a los usuarios al extremo que les devolverá su información sobre tiburones en el navegador.

      Por último, la función de list mostrará el contenido de la colección de vuelta al usuario. Añada el siguiente código debajo de la función create:

      ~/node_project/controllers/sharks.js

      ...
      exports.list = function (req, res) {
              Shark.find({}).exec(function (err, sharks) {
                      if (err) {
                              return res.send(500, err);
                      }
                      res.render('getshark', {
                              sharks: sharks
                   });
              });
      };
      

      Esta función utiliza el modelo Shark con el método model.find() de Mongoose para devolver los tiburones que se introdujeron en la colección sharks. Hace esto devolviendo el objeto de consulta; en este caso, todas las entradas de la colección sharks, como un compromiso, usando la función exec() de Mongoose. Si se produce un error, la función de devolución de llamada mostrará un error de 500.

      El objeto de consulta devuelto con la colección sharks se mostrará en una página getshark que crearemos en el siguiente paso usando el lenguaje de plantillas EJS.

      El archivo terminado tendrá este aspecto:

      ~/node_project/controllers/sharks.js

      const path = require('path');
      const Shark = require('../models/sharks');
      
      exports.index = function (req, res) {
          res.sendFile(path.resolve('views/sharks.html'));
      };
      
      exports.create = function (req, res) {
          var newShark = new Shark(req.body);
          console.log(req.body);
          newShark.save(function (err) {
                  if(err) {
                  res.status(400).send('Unable to save shark to database');
              } else {
                  res.redirect('/sharks/getshark');
              }
        });
                     };
      
      exports.list = function (req, res) {
              Shark.find({}).exec(function (err, sharks) {
                      if (err) {
                              return res.send(500, err);
                      }
                      res.render('getshark', {
                              sharks: sharks
                   });
              });
      };
      

      Tenga en cuenta que, aunque no usamos funciones de flecha aquí, quizá desee incluirlas conforme realice repeticiones en este código en su propio proceso de desarrollo.

      Guarde y cierre el archivo cuando concluya la edición.

      Antes de continuar con el siguiente paso, puede ejecutar tree de nuevo desde su directorio node_project para ver la estructura del proyecto en este punto. Esta vez, para evitar extendernos, le indicaremos a tree que omita el directorio node_modules usando la opción -I:

      Con las adiciones que realizó, la estructura de su proyecto tendrá este aspecto:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json └── views ├── css │ └── styles.css ├── index.html └── sharks.html

      Ahora que tiene un componente de controlador que dirija la forma de guardar datos y devolverlos al usuario, puede proceder a crear las vistas que implementarán la lógica de su controlador.

      Paso 5: Usar EJS y Express Middleware para recopilar y representar datos

      Para habilitar nuestra aplicación para trabajar con datos de usuario, realizaremos dos cosas: primero, incluiremos una función de middleware Express integrada, urlencoded(), que permitirá que nuestra aplicación analice los datos introducidos por nuestro usuario. En segundo lugar, añadiremos etiquetas de plantillas a nuestras vistas para habilitar la interacción dinámica con los datos del usuario en nuestro código.

      Para trabajar con la función urlencoded() de Express, primero abra su archivo app.js:

      Encima de su función express.static() agregue la siguiente línea:

      ~/node_project/app.js

      ...
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      ...
      

      La adición de esta función permitirá acceder a los datos analizados de POST de nuestro formulario de información sobre tiburones. Estamos especificando true con la opción extended para permitir una mayor flexibilidad en el tipo de datos que nuestra aplicación analizará (incluidos los elementos como los objetos anidados). Consulte la documentación de funciones para obtener más información sobre opciones.

      Guarde y cierre el archivo cuando concluya la edición.

      A continuación, añadiremos la funcionalidad de plantillas a nuestras vistas. Primero, instale el paquete ejs con npm install:

      A continuación, abra el archivo sharks.html en la carpeta de views:

      En el paso 3, examinamos esta página para determinar cómo debemos escribir nuestro esquema y modelo de Mongoose:

      Página de información de tiburones

      Ahora, en lugar de tener un diseño de dos columnas, introduciremos una tercera columna con un formulario en el que los usuarios puedan agregar información sobre tiburones.

      Como primer paso, cambie las dimensiones de las columnas existentes a 4 para crear tres columnas de igual tamaño. Tenga en cuenta que deberá realizar este cambio en las dos líneas que actualmente contienen el texto <div class="col-lg-6". Ambas se convertirán en <div class="col-lg-4>>:

      ~/node_project/views/sharks.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          </div>
        </div>
      
       </html>
      

      Para obtener una introducción al sistema de cuádriculas de Bootstrap, incluidos sus diseños de fila y columna, consulte esta introducción a Bootstrap.

      A continuación, añada otra columna que incluya el extremo con nombre para la solicitud de POST con los datos de tiburones del usuario y las etiquetas de la plantilla EJS que capturarán esos datos. Esta columna se colocará debajo de las etiquetas de cierre </p> y </div> de la columna anterior y encima de las etiquetas de cierre para la fila, el contenedor y el documento HTML. Estas etiquetas de cierre ya están implementadas en su código; también se muestran a continuación con comentarios. Déjelos implementados al añadir el siguiente código para crear la nueva columna:

      ~/node_project/views/sharks.html

      ...
             </p> <!-- closing p from previous column -->
         </div> <!-- closing div from previous column -->
      <div class="col-lg-4">
                  <p>
                      <form action="/sharks/addshark" method="post">
                          <div class="caption">Enter Your Shark</div>
                          <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                          <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                          <button type="submit">Submit</button>
                      </form>
                  </p>
              </div>
          </div> <!-- closing div for row -->
      </div> <!-- closing div for container -->
      
      </html> <!-- closing html tag -->
      

      En la etiqueta form, está añadiendo un extremo "/sharks/addshark" para los datos sobre tiburones del usuario y especificando el método de POST para enviarlo. En los campos de entrada, está especificando campos para "Sharks Name" y "Shark Character", los cuales corresponden al modelo de Shark que definió previamente.

      Para añadir las entradas del usuario a su colección sharks, utiliza etiquetas de la plantilla EJS (<%=, %>) junto con la sintaxis de JavaScript para asignar las entradas del usuario a los campos correspondientes en el documento recién creado. Para obtener más información sobre los objetos de JavaScript, consulte nuestro artículo Información sobre objetos de JavaScript. Para obtener más información sobre etiquetas de plantillas de EJS, consulte la documentación de EJS.

      Al finalizar, el contenedor completo con las tres columnas, incluida la columna con su formulario de entrada de tiburones, tendrá este aspecto:

      ~/node_project/views/sharks.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          <div class="col-lg-4">
                  <p>
                      <form action="/sharks/addshark" method="post">
                          <div class="caption">Enter Your Shark</div>
                          <input type="text" placeholder="Shark Name" name="name" <%=sharks[i].name; %>
                          <input type="text" placeholder="Shark Character" name="character" <%=sharks[i].character; %>
                          <button type="submit">Submit</button>
                      </form>
                  </p>
              </div>
          </div>
        </div>
      
      </html>
      

      Guarde y cierre el archivo cuando concluya la edición.

      Ahora que tiene una forma de recopilar las entradas de su usuario, puede crear un extremo para mostrar los tiburones mostrados y la información asociada sobre su carácter.

      Copie el archivo sharks.html recién modificado a un archivo llamado getshark.html:

      • cp views/sharks.html views/getshark.html

      Abra getshark.html:

      Dentro del archivo, modificaremos la columna que usamos para crear nuestro formulario de entrada de tiburones sustituyéndola por una columna que mostrará los tiburones en nuestra colección sharks. Una vez más, su código se aplicará entre las etiquetas existentes </p> y </div> de la columna anterior y las etiquetas de cierre de la fila, del contenedor y del documento HTML. Recuerde dejar estas etiquetas en su lugar al añadir el siguiente código para crear la columna:

      ~/node_project/views/getshark.html

      ...
             </p> <!-- closing p from previous column -->
         </div> <!-- closing div from previous column -->
      <div class="col-lg-4">
                 <p>
                    <div class="caption">Your Sharks</div>
                        <ul>
                           <% sharks.forEach(function(shark) { %>
                              <p>Name: <%= shark.name %></p>
                              <p>Character: <%= shark.character %></p>
                           <% }); %>
                        </ul>
                  </p>
              </div>
          </div> <!-- closing div for row -->
      </div> <!-- closing div for container -->
      
      </html> <!-- closing html tag -->
      

      Aquí utiliza etiquetas de plantilla EJS y el método forEach() para mostrar cada valor de su colección sharks, incluida información sobre el tiburón que se añadió.

      Al finalizar, el contenedor completo con las tres columnas, incluida la columna con la colección sharks, tendrá este aspecto:

      ~/node_project/views/getshark.html

      ...
      <div class="container">
          <div class="row">
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Some sharks are known to be dangerous to humans, though many more are not. The sawshark, for example, is not considered a threat to humans.
                      </div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sawshark.jpg" alt="Sawshark">
                  </p>
              </div>
              <div class="col-lg-4">
                  <p>
                      <div class="caption">Other sharks are known to be friendly and welcoming!</div>
                      <img src="https://assets.digitalocean.com/articles/docker_node_image/sammy.png" alt="Sammy the Shark">
                  </p>
              </div>
          <div class="col-lg-4">
                  <p>
                    <div class="caption">Your Sharks</div>
                        <ul>
                           <% sharks.forEach(function(shark) { %>
                              <p>Name: <%= shark.name %></p>
                              <p>Character: <%= shark.character %></p>
                           <% }); %>
                        </ul>
                  </p>
              </div>
          </div>
        </div>
      
      </html>
      

      Guarde y cierre el archivo cuando concluya la edición.

      Para que la aplicación utilice las plantillas que creó, deberá añadir algunas líneas a su archivo app.js. Ábralo de nuevo:

      Encima del punto en que agregó la función express.urlencoded(), agregue las siguientes líneas:

      ~/node_project/app.js

      ...
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      
      ...
      

      El método app.engine indica a la aplicación que asigne el motor de la plantilla EJS a los archivos HTML, mientras que app.set define el motor de vista predeterminado.

      Ahora, su archivo app.js tendrá el siguiente aspecto:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      const db = require('./db');
      
      const path = __dirname + '/views/';
      const port = 8080;
      
      router.use(function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      
      router.get('/',function(req,res){
        res.sendFile(path + 'index.html');
      });
      
      router.get('/sharks',function(req,res){
        res.sendFile(path + 'sharks.html');
      });
      
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      app.use('/', router);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Ahora que creó vistas que pueden funcionar de forma dinámica con datos de usuarios, es el momento de crear las rutas de su proyecto para unir sus vistas y la lógica del controlador.

      Paso 6: Crear rutas

      El paso final para establecer los componentes de la aplicación será crear rutas. Separaremos nuestras rutas por funciones incluidas una ruta a la página de destino de nuestra aplicación y otra ruta a nuestra página de tiburones. En nuestra ruta sharks integraremos la lógica de nuestro controlador con las vistas que creamos en el paso anterior.

      Primero, cree un directorio routes:

      A continuación, abra un archivo llamado index.js en este directorio:

      Este archivo primero importará los objetos express, router y path, lo que nos permitirá definir las rutas que deseamos exportar con el objeto router y permitir que funcione de forma dinámica con rutas de archivo. Añada el siguiente código en la parte superior del archivo:

      ~/node_project/routes/index.js

      const express = require('express');
      const router = express.Router();
      const path = require('path');
      

      A continuación agregue la función router.use, la cual carga una función de middleware que registrará las solicitudes del router y las transmitirá a las rutas de la aplicación.

      ~/node_project/routes/index.js

      ...
      
      router.use (function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      

      Las solicitudes al root de nuestra aplicación se dirigirán aquí primero, y desde este punto los usuarios accederán a la página de inicio de nuestra aplicación, la ruta que definiremos a continuación. Añada el siguiente código debajo de la función router.use para definir la ruta a la página de destino:

      ~/node_project/routes/index.js

      ...
      
      router.get('/',function(req,res){
        res.sendFile(path.resolve('views/index.html'));
      });
      

      Cuando los usuarios visitan nuestra aplicación, el primer lugar al que queremos enviarlos es la página de inicio index.html que tenemos en nuestro directorio views.

      Por último, para que sea posible acceder a estas rutas como módulos importables en otros puntos de la aplicación, agregue una expresión de cierre al final del archivo para exportar el objeto router:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      El archivo terminado tendrá este aspecto:

      ~/node_project/routes/index.js

      const express = require('express');
      const router = express.Router();
      const path = require('path');
      
      router.use (function (req,res,next) {
        console.log('/' + req.method);
        next();
      });
      
      router.get('/',function(req,res){
        res.sendFile(path.resolve('views/index.html'));
      });
      
      module.exports = router;
      

      Guarde y cierre este archivo cuando concluya la edición.

      A continuación, abra un archivo llamado sharks.js para definir la forma en que la aplicación debería usar los diferentes extremos y vistas que creamos para trabajar con la entrada sobre tiburones de nuestro usuario:

      En la parte superior del archivo, importe los objetos express y router:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      

      A continuación, importe un módulo llamado shark que le permitirá trabajar con las funciones exportadas que definió con su controlador:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      const shark = require('../controllers/sharks');
      

      Ahora puede crear rutas usando las funciones index, create y list que definó en su archivo de controlador sharks. Cada ruta se asociará al método HTTP adecuado: GET para la representación de la principal página de inicio de información sobre tiburones y la presentación de la lista de tiburones al usuario, y POST para la creación de una nueva entrada sobre tiburones:

      ~/node_project/routes/sharks.js

      ...
      
      router.get('/', function(req, res){
          shark.index(req,res);
      });
      
      router.post('/addshark', function(req, res) {
          shark.create(req,res);
      });
      
      router.get('/getshark', function(req, res) {
          shark.list(req,res);
      });
      

      Cada ruta utiliza la función relacionada en controllers/sharks.js, ya que hicimos posible el acceso a ese módulo al importarlo en la parte superior de este archivo.

      Por último, cierre el archivo vinculando estas rutas al objeto router y exportándolas:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      El archivo terminado tendrá este aspecto:

      ~/node_project/routes/sharks.js

      const express = require('express');
      const router = express.Router();
      const shark = require('../controllers/sharks');
      
      router.get('/', function(req, res){
          shark.index(req,res);
      });
      
      router.post('/addshark', function(req, res) {
          shark.create(req,res);
      });
      
      router.get('/getshark', function(req, res) {
          shark.list(req,res);
      });
      
      module.exports = router;
      

      Guarde y cierre el archivo cuando concluya la edición.

      El último paso para que su aplicación pueda acceder a estas rutas será añadirlas a app.js. Abra el archivo de nuevo:

      Debajo de su constante db, agregue la siguiente importación para sus rutas:

      ~/node_project/app.js

      ...
      const db = require('./db');
      const sharks = require('./routes/sharks');
      

      A continuación, sustituya la función app.use que actualmente monta su objeto router por la siguiente línea, que montará el módulo router de sharks:

      ~/node_project/app.js

      ...
      app.use(express.static(path));
      app.use('/sharks', sharks);
      
      app.listen(port, function () {
              console.log("Example app listening on port 8080!")
      })
      

      Ahora puede eliminar las rutas que se definieron previamente en este archivo, ya que importará las rutas de su aplicación usando el módulo router de sharks.

      La versión final de su archivo app.js tendrá este aspecto:

      ~/node_project/app.js

      const express = require('express');
      const app = express();
      const router = express.Router();
      const db = require('./db');
      const sharks = require('./routes/sharks');
      
      const path = __dirname + '/views/';
      const port = 8080;
      
      app.engine('html', require('ejs').renderFile);
      app.set('view engine', 'html');
      app.use(express.urlencoded({ extended: true }));
      app.use(express.static(path));
      app.use('/sharks', sharks);
      
      app.listen(port, function () {
        console.log('Example app listening on port 8080!')
      })
      

      Guarde y cierre el archivo cuando concluya la edición.

      Ahora puede ejecutar tree de nuevo para ver la estructura final de su proyecto:

      Así, la estructura del proyecto tendrá el siguiente aspecto:

      Output

      ├── Dockerfile ├── README.md ├── app.js ├── controllers │ └── sharks.js ├── db.js ├── models │ └── sharks.js ├── package-lock.json ├── package.json ├── routes │ ├── index.js │ └── sharks.js └── views ├── css │ └── styles.css ├── getshark.html ├── index.html └── sharks.html

      Una vez creados e implementados todos los componentes de su aplicación, estará listo para añadir un tiburón de prueba a su base de datos.

      Si siguió el tutorial de configuración inicial para servidores de los requisitos previos, deberá modificar su firewall, ya que actualmente solo permite el tráfico SSH. Para permitir el tráfico al puerto 8080 ejecute lo siguiente:

      Inicie la aplicación:

      A continuación, visite la dirección http://your_server_ip:8080 en su navegador. Visualizará la siguiente página de inicio:

      Página de inicio de la aplicación

      Haga clic en el botón Get Shark Info. Verá la siguiente página de información, con el formulario de entrada de tiburones añadido:

      Formulario de Shark Info

      En el formulario, agregue un tiburón que elija. A los efectos de esta demostración, añadiremos Megalodon Shark en el campo Shark Name y Ancient en el campo Shark Character:

      Formulario de Shark completado

      Haga clic en el botón Submit. Visualizará una página con esta información sobre tiburones que se le mostrará de nuevo:

      Resultado de tiburones

      También verá en su consola resultados que indican que el tiburón se agregó a su colección:

      Output

      Example app listening on port 8080! { name: 'Megalodon Shark', character: 'Ancient' }

      Si desea crear una nueva entrada de tiburones, regrese a la página Sharks y repita el proceso de adición de un tiburón.

      Con esto, dispondrá de una aplicación de información sobre tiburones activa que permite a los usuarios añadir información sobre sus tiburones favoritos.

      Conclusión

      A través de este tutorial, creó una aplicación de Node integrando una base de datos de MongoDB y reescribiendo la lógica de la aplicación con el patrón de arquitectura de MVC. Esta aplicación puede ser un buen punto de partida para una aplicación CRUD completa.

      Para acceder a más recursos sobre el patrón de MVC en otros contextos, consulte nuestra Serie de desarrollo de Django o Cómo crear una aplicación web moderna para administrar información de clientes con Django y React en Ubuntu 18.04.

      Para obtener más información sobre cómo trabajar con MongoDB, consulte nuestra biblioteca de tutoriales de MongoDB.



      Source link