One place for hosting & domains

      MongoDB

      How To Build and Deploy a GraphQL Server with Node.js and MongoDB on Ubuntu 18.04


      The author selected the Wikimedia Foundation to receive a donation as part of the Write for DOnations program.

      Introduction

      GraphQL was publicly released by Facebook in 2015 as a query language for APIs that makes it easy to query and mutate data from different data collections. From a single endpoint, you can query and mutate multiple data sources with a single POST request. GraphQL solves some of the common design flaws in REST API architectures, such as situations where the endpoint returns more information than you actually need. Also, it is possible when using REST APIs you would need to send requests to multiple REST endpoints to collect all the information you require—a situation that is called the n+1 problem. An example of this would be when you want to show a users’ information, but need to collect data such as personal details and addresses from different endpoints.

      These problems don’t apply to GraphQL as it has only one endpoint, which can return data from multiple collections. The data it returns depends on the query that you send to this endpoint. In this query you define the structure of the data you want to receive, including any nested data collections. In addition to a query, you can also use a mutation to change data on a GraphQL server, and a subscription to watch for changes in the data. For more information about GraphQL and its concepts, you can visit the documentation on the official website.

      As GraphQL is a query language with a lot of flexibility, it combines especially well with document-based databases like MongoDB. Both technologies are based on hierarchical, typed schemas and are popular within the JavaScript community. Also, MongoDB’s data is stored as JSON objects, so no additional parsing is necessary on the GraphQL server.

      In this tutorial, you’ll build and deploy a GraphQL server with Node.js that can query and mutate data from a MongoDB database that is running on Ubuntu 18.04. At the end of this tutorial, you’ll be able to access data in your database by using a single endpoint, both by sending requests to the server directly through the terminal and by using the pre-made GraphiQL playground interface. With this playground you can explore the contents of the GraphQL server by sending queries, mutations, and subscriptions. Also, you can find visual representations of the schemas that are defined for this server.

      At the end of this tutorial, you’ll use the GraphiQL playground to quickly interface with your GraphQL server:

      The GraphiQL playground in action

      Prerequisites

      Before you begin this guide you’ll need the following:

      Step 1 — Setting Up the MongoDB Database

      Before creating the GraphQL server, make sure your database is configured right, has authentication enabled, and is filled with sample data. For this you need to connect to the Ubuntu 18.04 server running the MongoDB database from your command prompt. All steps in this tutorial will take place on this server.

      After you’ve established the connection, run the following command to check if MongoDB is active and running on your server:

      • sudo systemctl status mongodb

      You’ll see the following output in your terminal, indicating the MongoDB database is actively running:

      Output

      ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2019-02-23 12:23:03 UTC; 1 months 13 days ago Docs: man:mongod(1) Main PID: 2388 (mongod) Tasks: 25 (limit: 1152) CGroup: /system.slice/mongodb.service └─2388 /usr/bin/mongod --unixSocketPrefix=/run/mongodb --config /etc/mongodb.conf

      Before creating the database where you’ll store the sample data, you need to create an admin user first, since regular users are scoped to a specific database. You can do this by executing the following command that opens the MongoDB shell:

      With the MongoDB shell you'll get direct access to the MongoDB database and can create users or databases and query data. Inside this shell, execute the following command that will add a new admin user to MongoDB. You can replace the highlighted keywords with your own username and password combination, but don't forget to write them down somewhere.

      • use admin
      • db.createUser({
      • user: "admin_username",
      • pwd: "admin_password",
      • roles: [{ role: "root", db: "admin"}]
      • })

      The first line of the preceding command selects the database called admin, which is the database where all the admin roles are stored. With the method db.createUser() you can create the actual user and define its username, password, and roles.

      Executing this command will return:

      Output

      Successfully added user: { "user" : "admin_username", "roles" : [ { "role" : "root", "db" : "admin" } ] }

      You can now close the MongoDB shell by typing exit.

      Next, log in at the MongoDB shell again, but this time with the newly created admin user:

      • mongo -u "admin_username" -p "admin_password" --authenticationDatabase "admin"

      This command will open the MongoDB shell as a specific user, where the -u flag specifies the username and the -p flag the password of that user. The extra flag --authenticationDatabase specifies that you want to log in as an admin.

      Next, you'll switch to a new database and then use the db.createUser() method to create a new user with permissions to make changes to this database. Replace the highlighted sections with your own information, making sure to write these credentials down.

      Run the following command in the MongoDB shell:

      • use database_name
      • db.createUser({
      • user: "username",
      • pwd: "password",
      • roles: ["readWrite"]
      • })

      This will return the following:

      Output

      Successfully added user: { "user" : "username", "roles" : ["readWrite"] }

      After creating the database and user, fill this database with sample data that can be queried by the GraphQL server later on in this tutorial. For this, you can use the bios collection sample from the MongoDB website. By executing the commands in the following code snippet you'll insert a smaller version of this bios collection dataset into your database. You can replace the highlighted sections with your own information, but for the purposes of this tutorial, name the collection bios:

      • db.bios.insertMany([
      • {
      • "_id" : 1,
      • "name" : {
      • "first" : "John",
      • "last" : "Backus"
      • },
      • "birth" : ISODate("1924-12-03T05:00:00Z"),
      • "death" : ISODate("2007-03-17T04:00:00Z"),
      • "contribs" : [
      • "Fortran",
      • "ALGOL",
      • "Backus-Naur Form",
      • "FP"
      • ],
      • "awards" : [
      • {
      • "award" : "W.W. McDowell Award",
      • "year" : 1967,
      • "by" : "IEEE Computer Society"
      • },
      • {
      • "award" : "National Medal of Science",
      • "year" : 1975,
      • "by" : "National Science Foundation"
      • },
      • {
      • "award" : "Turing Award",
      • "year" : 1977,
      • "by" : "ACM"
      • },
      • {
      • "award" : "Draper Prize",
      • "year" : 1993,
      • "by" : "National Academy of Engineering"
      • }
      • ]
      • },
      • {
      • "_id" : ObjectId("51df07b094c6acd67e492f41"),
      • "name" : {
      • "first" : "John",
      • "last" : "McCarthy"
      • },
      • "birth" : ISODate("1927-09-04T04:00:00Z"),
      • "death" : ISODate("2011-12-24T05:00:00Z"),
      • "contribs" : [
      • "Lisp",
      • "Artificial Intelligence",
      • "ALGOL"
      • ],
      • "awards" : [
      • {
      • "award" : "Turing Award",
      • "year" : 1971,
      • "by" : "ACM"
      • },
      • {
      • "award" : "Kyoto Prize",
      • "year" : 1988,
      • "by" : "Inamori Foundation"
      • },
      • {
      • "award" : "National Medal of Science",
      • "year" : 1990,
      • "by" : "National Science Foundation"
      • }
      • ]
      • }
      • ]);

      This code block is an array consisting of multiple objects that contain information about successful scientists from the past. After running these commands to enter this collection into your database, you'll receive the following message indicating the data was added:

      Output

      { "acknowledged" : true, "insertedIds" : [ 1, ObjectId("51df07b094c6acd67e492f41") ] }

      After seeing the success message, you can close the MongoDB shell by typing exit. Next, configure the MongoDB installation to have authorization enabled so only authenticated users can access the data. To edit the configuration of the MongoDB installation, open the file containing the settings for this installation:

      • sudo nano /etc/mongodb.conf

      Uncomment the highlighted line in the following code to enable authorization:

      /etc/mongodb.conf

      ...
      # Turn on/off security.  Off is currently the default
      #noauth = true
      auth = true
      ...
      

      In order to make these changes active, restart MongoDB by running:

      • sudo systemctl restart mongodb

      Make sure the database is running again by executing the command:

      • sudo systemctl status mongodb

      This will yield output similar to the following:

      Output

      ● mongodb.service - An object/document-oriented database Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled) Active: active (running) since Sat 2019-02-23 12:23:03 UTC; 1 months 13 days ago Docs: man:mongod(1) Main PID: 2388 (mongod) Tasks: 25 (limit: 1152) CGroup: /system.slice/mongodb.service └─2388 /usr/bin/mongod --unixSocketPrefix=/run/mongodb --config /etc/mongodb.conf

      To make sure that your user can connect to the database you just created, try opening the MongoDB shell as an authenticated user with the command:

      • mongo -u "username" -p "password" --authenticationDatabase "database_name"

      This uses the same flags as before, only this time the --authenticationDatabase is set to the database you've created and filled with the sample data.

      Now you've successfully added an admin user and another user that has read/write access to the database with the sample data. Also, the database has authorization enabled meaning you need a username and password to access it. In the next step you'll create the GraphQL server that will be connected to this database later in the tutorial.

      Step 2 — Creating the GraphQL Server

      With the database configured and filled with sample data, it's time to create a GraphQL server that can query and mutate this data. For this you'll use Express and express-graphql, which both run on Node.js. Express is a lightweight framework to quickly create Node.js HTTP servers, and express-graphql provides middleware to make it possible to quickly build GraphQL servers.

      The first step is to make sure your machine is up to date:

      Next, install Node.js on your server by running the following commands. Together with Node.js you'll also install npm, a package manager for JavaScript that runs on Node.js.

      • sudo apt install nodejs npm

      After following the installation process, check if the Node.js version you've just installed is v8.10.0 or higher:

      This will return the following:

      Output

      v8.10.0

      To initialize a new JavaScript project, run the following commands on the server as a sudo user, and replace the highlighted keywords with a name for your project.

      First move into the root directory of your server:

      Once there, create a new directory named after your project:

      Move into this directory:

      Finally, initialize a new npm package with the following command:

      After running npm init -y you'll receive a success message that the following package.json file was created:

      Output

      Wrote to /home/username/project_name/package.json: { "name": "project_name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { "test": "echo "Error: no test specified" && exit 1" }, "keywords": [], "author": "", "license": "ISC" }

      Note: You can also execute npm init without the -y flag, after which you would answer multiple questions to set up the project name, author, etc. You can enter the details or just press enter to proceed.

      Now that you've initialized the project, install the packages you need to set up the GraphQL server:

      • sudo npm install --save express express-graphql graphql

      Create a new file called index.js and subsequently open this file by running:

      Next, add the following code block into the newly created file to set up the GraphQL server:

      index.js

      const express = require('express');
      const graphqlHTTP = require('express-graphql');
      const { buildSchema } = require('graphql');
      
      // Construct a schema, using GraphQL schema language
      const schema = buildSchema(`
        type Query {
          hello: String
        }
      `);
      
      // Provide resolver functions for your schema fields
      const resolvers = {
        hello: () => 'Hello world!'
      };
      
      const app = express();
      app.use('/graphql', graphqlHTTP({
        schema,
        rootValue: resolvers
      }));
      app.listen(4000);
      
      console.log(`🚀 Server ready at http://localhost:4000/graphql`);
      

      This code block consists of several parts that are all important. First you describe the schema of the data that is returned by the GraphQL API:

      index.js

      ...
      // Construct a schema, using GraphQL schema language
      const schema = buildSchema(`
        type Query {
          hello: String
        }
      `);
      ...
      

      The type Query defines what queries can be executed and in which format it will return the result. As you can see, the only query defined is hello that returns data in a String format.

      The next section establishes the resolvers, where data is matched to the schemas that you can query:

      index.js

      ...
      // Provide resolver functions for your schema fields
      const resolvers = {
        hello: () => 'Hello world!'
      };
      ...
      

      These resolvers are directly linked to schemas, and return the data that matches these schemas.

      The final part of this code block initializes the GraphQL server, creates the API endpoint with Express, and describes the port on which the GraphQL endpoint is running:

      index.js

      ...
      const app = express();
      app.use('/graphql', graphqlHTTP({
        schema,
        rootValue: resolvers
      }));
      app.listen(4000);
      
      console.log(`🚀 Server ready at http://localhost:4000/graphql`);
      

      After you have added these lines, save and exit from index.js.

      Next, to actually run the GraphQL server you need to run the file index.js with Node.js. This can be done manually from the command line, but it's common practice to set up the package.json file to do this for you.

      Open the package.json file:

      Add the following highlighted line to this file:

      package.json

      {
        "name": "project_name",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "start": "node index.js",
          "test": "echo "Error: no test specified" && exit 1"
        },
        "keywords": [],
        "author": "",
        "license": "ISC"
      }
      

      Save and exit the file.

      To start the GraphQL server, execute the following command in the terminal:

      Once you run this, the terminal prompt will disappear, and a message will appear to confirm the GraphQL server is running:

      Output

      🚀 Server ready at http://localhost:4000/graphql

      If you now open up another terminal session, you can test if the GraphQL server is running by executing the following command. This sends a curl POST request with a JSON body after the --data flag that contains your GraphQL query to the local endpoint:

      • curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ hello }" }' http://localhost:4000/graphql

      This will execute the query as it's described in the GraphQL schema in your code and return data in a predictable JSON format that is equal to the data as it's returned in the resolvers:

      Output

      { "data": { "hello": "Hello world!" } }

      Note: In case the Express server crashes or gets stuck, you need to manually kill the node process that is running on the server. To kill all such processes, you can execute the following:

      After which, you can restart the GraphQL server by running:

      In this step you've created the first version of the GraphQL server that is now running on a local endpoint that can be accessed on your server. Next, you'll connect your resolvers to the MongoDB database.

      Step 3 — Connecting to the MongoDB Database

      With the GraphQL server in order, you can now set up the connection with the MongoDB database that you configured and filled with data before and create a new schema that matches this data.

      To be able to connect to MongoDB from the GraphQL server, install the JavaScript package for MongoDB from npm:

      • sudo npm install --save mongodb

      Once this has been installed, open up index.js in your text editor:

      Next, add the following highlighted code to index.js just after the imported dependencies and fill the highlighted values with your own connection details to the local MongoDB database. The username, password, and database_name are those that you created in the first step of this tutorial.

      index.js

      const express = require('express');
      const graphqlHTTP = require('express-graphql');
      const { buildSchema } = require('graphql');
      const { MongoClient } = require('mongodb');
      
      const context = () => MongoClient.connect('mongodb://username:password@localhost:27017/database_name', { useNewUrlParser: true }).then(client => client.db('database_name'));
      ...
      

      These lines add the connection to the local MongoDB database to a function called context. This context function will be available to every resolver, which is why you use this to set up database connections.

      Next, in your index.js file, add the context function to the initialization of the GraphQL server by inserting the following highlighted lines:

      index.js

      ...
      const app = express();
      app.use('/graphql', graphqlHTTP({
        schema,
        rootValue: resolvers,
        context
      }));
      app.listen(4000);
      
      console.log(`🚀 Server ready at http://localhost:4000/graphql`);
      

      Now you can call this context function from your resolvers, and thereby read variables from the MongoDB database. If you look back to the first step of this tutorial, you can see which values are present in the database. From here, define a new GraphQL schema that matches this data structure. Overwrite the previous value for the constant schema with the following highlighted lines:

      index.js

      ...
      // Construct a schema, using GrahQL schema language
      const schema = buildSchema(`
        type Query {
          bios: [Bio]
        }
        type Bio {
          name: Name,
          title: String,
          birth: String,
          death: String,
          awards: [Award]
        }
        type Name {
          first: String,
          last: String
        },
        type Award {
          award: String,
          year: Float,
          by: String
        }
      `);
      ...
      

      The type Query has changed and now returns a collection of the new type Bio. This new type consists of several types including two other non-scalar types Name and Awards, meaning these types don't match a predefined format like String or Float. For more information on defining GraphQL schemas you can look at the documentation for GraphQL.

      Also, since the resolvers tie the data from the database to the schema, update the code for the resolvers when you make changes to the schema. Create a new resolver that is called bios, which is equal to the Query that can be found in the schema and the name of the collection in the database. Note that, in this case, the name of the collection in db.collection('bios') is bios, but that this would change if you had assigned a different name to your collection.

      Add the following highlighted line to index.js:

      index.js

      ...
      // Provide resolver functions for your schema fields
      const resolvers = {
        bios: (args, context) => context().then(db => db.collection('bios').find().toArray())
      };
      ...
      

      This function will use the context function, which you can use to retrieve variables from the MongoDB database. Once you have made these changes to the code, save and exit index.js.

      In order to make these changes active, you need to restart the GraphQL server. You can stop the current process by using the keyboard combination CTRL + C and start the GraphQL server by running:

      Now you're able to use the updated schema and query the data that is inside the database. If you look at the schema, you'll see that the Query for bios returns the type Bio; this type could also return the type Name.

      To return all the first and last names for all the bios in the database, send the following request to the GraphQL server in a new terminal window:

      • curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ bios { name { first, last } } }" }' http://localhost:4000/graphql

      This again will return a JSON object that matches the structure of the schema:

      Output

      {"data":{"bios":[{"name":{"first":"John","last":"Backus"}},{"name":{"first":"John","last":"McCarthy"}}]}}

      You can easily retrieve more variables from the bios by extending the query with any of the types that are described in the type for Bio.

      Also, you can retrieve a bio by specifying an id. In order to do this you need to add another type to the Query type and extend the resolvers. To do this, open index.js in your text editor:

      Add the following highlighted lines of code:

      index.js

      ...
      // Construct a schema, using GrahQL schema language
      const schema = buildSchema(`
        type Query {
          bios: [Bio]
          bio(id: Int): Bio
        }
      
        ...
      
        // Provide resolver functions for your schema fields
        const resolvers = {
          bios: (args, context) => context().then(db => db.collection('bios').find().toArray()),
          bio: (args, context) => context().then(db => db.collection('bios').findOne({ _id: args.id }))
        };
        ...
      

      Save and exit the file.

      In the terminal that is running your GraphQL server, press CTRL + C to stop it from running, then execute the following to restart it:

      In another terminal window, execute the following GraphQL request:

      • curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ bio(id: 1) { name { first, last } } }" }' http://localhost:4000/graphql

      This returns the entry for the bio that has an id equal to 1:

      Output

      { "data": { "bio": { "name": { "first": "John", "last": "Backus" } } } }

      Being able to query data from a database is not the only feature of GraphQL; you can also change the data in the database. To do this, open up index.js:

      Next to the type Query you can also use the type Mutation, which allows you to mutate the database. To use this type, add it to the schema and also create input types by inserting these highlighted lines:

      index.js

      ...
      // Construct a schema, using GraphQL schema language
      const schema = buildSchema(`
        type Query {
          bios: [Bio]
          bio(id: Int): Bio
        }
        type Mutation {
          addBio(input: BioInput) : Bio
        }
        input BioInput {
          name: NameInput
          title: String
          birth: String
          death: String
        }
        input NameInput {
          first: String
          last: String
        }
      ...
      

      These input types define which variables can be used as inputs, which you can access in the resolvers and use to insert a new document in the database. Do this by adding the following lines to index.js:

      index.js

      ...
      // Provide resolver functions for your schema fields
      const resolvers = {
        bios: (args, context) => context().then(db => db.collection('bios').find().toArray()),
        bio: (args, context) => context().then(db => db.collection('bios').findOne({ _id: args.id })),
        addBio: (args, context) => context().then(db => db.collection('bios').insertOne({ name: args.input.name, title: args.input.title, death: args.input.death, birth: args.input.birth})).then(response => response.ops[0])
      };
      ...
      

      Just as with the resolvers for regular queries, you need to return a value from the resolver in index.js. In the case of a Mutation where the type Bio is mutated, you would return the value of the mutated bio.

      At this point, your index.js file will contain the following lines:

      index.js

      iconst express = require('express');
      const graphqlHTTP = require('express-graphql');
      const { buildSchema } = require('graphql');
      const { MongoClient } = require('mongodb');
      
      const context = () => MongoClient.connect('mongodb://username:password@localhost:27017/database_name', { useNewUrlParser: true })
        .then(client => client.db('GraphQL_Test'));
      
      // Construct a schema, using GraphQL schema language
      const schema = buildSchema(`
        type Query {
          bios: [Bio]
          bio(id: Int): Bio
        }
        type Mutation {
          addBio(input: BioInput) : Bio
        }
        input BioInput {
          name: NameInput
          title: String
          birth: String
          death: String
        }
        input NameInput {
          first: String
          last: String
        }
        type Bio {
          name: Name,
          title: String,
          birth: String,
          death: String,
          awards: [Award]
        }
        type Name {
          first: String,
          last: String
        },
        type Award {
          award: String,
          year: Float,
          by: String
        }
      `);
      
      // Provide resolver functions for your schema fields
      const resolvers = {
        bios: (args, context) =>context().then(db => db.collection('Sample_Data').find().toArray()),
        bio: (args, context) =>context().then(db => db.collection('Sample_Data').findOne({ _id: args.id })),
        addBio: (args, context) => context().then(db => db.collection('Sample_Data').insertOne({ name: args.input.name, title: args.input.title, death: args.input.death, birth: args.input.birth})).then(response => response.ops[0])
      };
      
      const app = express();
      app.use('/graphql', graphqlHTTP({
        schema,
        rootValue: resolvers,
        context
      }));
      app.listen(4000);
      
      console.log(`🚀 Server ready at http://localhost:4000/graphql`);
      

      Save and exit index.js.

      To check if your new mutation is working, restart the GraphQL server by pressing CTRL + c and running npm start in the terminal that is running your GraphQL server, then open another terminal session to execute the following curl request. Just as with the curl request for queries, the body in the --data flag will be sent to the GraphQL server. The highlighted parts will be added to the database:

      • curl -X POST -H "Content-Type: application/json" --data '{ "query": "mutation { addBio(input: { name: { first: "test", last: "user" } }) { name { first, last } } }" }' http://localhost:4000/graphql

      This returns the following result, meaning you just inserted a new bio to the database:

      Output

      { "data": { "addBio": { "name": { "first": "test", "last": "user" } } } }

      In this step, you created the connection with MongoDB and the GraphQL server, allowing you to retrieve and mutate data from this database by executing GraphQL queries. Next, you'll expose this GraphQL server for remote access.

      Step 4 — Allowing Remote Access

      Having set up the database and the GraphQL server, you can now configure the GraphQL server to allow remote access. For this you'll use Nginx, which you set up in the prerequisite tutorial How to install Nginx on Ubuntu 18.04. This Nginx configuration can be found in the /etc/nginx/sites-available/example.com file, where example.com is the server name you added in the prerequisite tutorial.

      Open this file for editing, replacing your domain name with example.com:

      • sudo nano /etc/nginx/sites-available/example.com

      In this file you can find a server block that listens to port 80, where you've already set up a value for server_name in the prerequisite tutorial. Inside this server block, change the value for root to be the directory in which you created the code for the GraphQL server and add index.js as the index. Also, within the location block, set a proxy_pass so you can use your server's IP or a custom domain name to refer to the GraphQL server:

      /etc/nginx/sites-available/example.com

      server {
        listen 80;
        listen [::]:80;
      
        root /project_name;
        index index.js;
      
        server_name example.com;
      
        location / {
          proxy_pass http://localhost:4000/graphql;
        }
      }
      

      Make sure there are no Nginx syntax errors in this configuration file by running:

      You will receive the following output:

      Output

      nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful

      When there are no errors found for the configuration file, restart Nginx:

      • sudo systemctl restart nginx

      Now you will be able to access your GraphQL server from any terminal session tab by executing and replacing example.com by either your server's IP or your custom domain name:

      • curl -X POST -H "Content-Type: application/json" --data '{ "query": "{ bios { name { first, last } } }" }' http://example.com

      This will return the same JSON object as the one of the previous step, including any additional data you might have added by using a mutation:

      Output

      {"data":{"bios":[{"name":{"first":"John","last":"Backus"}},{"name":{"first":"John","last":"McCarthy"}},{"name":{"first":"test","last":"user"}}]}}

      Now that you have made your GraphQL server accessible remotely, make sure your GraphQL server doesn't go down when you close the terminal or the server restarts. This way, your MongoDB database will be accessible via the GraphQL server whenever you want to make a request.

      To do this, use the npm package forever, a CLI tool that ensures that your command line scripts run continuously, or get restarted in case of any failure.

      Install forever with npm:

      • sudo npm install forever -g

      Once it is done installing, add it to the package.json file:

      package.json

      {
        "name": "project_name",
        "version": "1.0.0",
        "description": "",
        "main": "index.js",
        "scripts": {
          "start": "node index.js",
          "deploy": "forever start --minUptime 2000 --spinSleepTime 5 index.js",
          "test": "echo "Error: no test specified" && exit 1"
        },
        ...
      

      To start the GraphQL server with forever enabled, run the following command:

      This will start the index.js file containing the GraphQL server with forever, and ensure it will keep running with a minimum uptime of 2000 milliseconds and 5 milliseconds between every restart in case of a failure. The GraphQL server will now continuously run in the background, so you don't need to open a new tab any longer when you want to send a request to the server.

      You've now created a GraphQL server that is using MongoDB to store data and is set up to allow access from a remote server. In the next step you'll enable the GraphiQL playground, which will make it easier for you to inspect the GraphQL server.

      Step 5 — Enabling GraphiQL Playground

      Being able to send cURL requests to the GraphQL server is great, but it would be faster to have a user interface that can execute GraphQL requests immediately, especially during development. For this you can use GraphiQL, an interface supported by the package express-graphql.

      To enable GraphiQL, edit the file index.js:

      Add the following highlighted lines:

      index.js

      const app = express();
      app.use('/graphql', graphqlHTTP({
        schema,
        rootValue: resolvers,
        context,
        graphiql: true
      }));
      app.listen(4000);
      
      console.log(`🚀 Server ready at http://localhost:4000/graphql`);
      

      Save and exit the file.

      In order for these changes to become visible, make sure to stop forever by executing:

      Next, start forever again so the latest version of your GraphQL server is running:

      Open a browser at the URL http://example.com, replacing example.com with your domain name or your server IP. You will see the GraphiQL playground, where you can type GraphQL requests.

      The initial screen for the GraphiQL playground

      On the left side of this playground you can type the GraphQL queries and mutations, while the output will be shown on the right side of the playground. To test if this is working, type the following query on the left side:

      query {
        bios {
          name {
            first
            last
          }
        }
      }
      

      This will output the same result on the right side of the playground, again in JSON format:

      The GraphiQL playground in action

      Now you can send GraphQL requests using the terminal and the GraphiQL playground.

      Conclusion

      In this tutorial you've set up a MongoDB database and retrieved and mutated data from this database using GraphQL, Node.js, and Express for the server. Additionally, you configured Nginx to allow remote access to this server. Not only can you send requests to this GraphQL server directly, you can also use the GraphiQL as a visual, in-browser GraphQL interface.

      If you want to learn about GraphQL, you can watch a recording of my presentation on GraphQL at NDC {London} or visit the website howtographql.com for tutorials about GraphQL. To study how GraphQL interacts with other technologies, check out the tutorial on How to Manually Set Up a Prisma Server on Ubuntu 18.04, and for more information on building applications with MongoDB, see How To Build a Blog with Nest.js, MongoDB, and Vue.js.



      Source link

      How To Build a Blog with Nest.js, MongoDB, and Vue.js


      The author selected Software in the Public Interest Inc to receive a donation as part of the Write for DOnations program.

      Introduction

      Nest.js is a scalable, server-side JavaScript framework built with TypeScript that still preserves compatibility with JavaScript, which makes it an effective tool for building efficient and reliable back-end applications. It has a modular architecture that provides a mature, structural design pattern to the Node.js development world.

      Vue.js is a front-end JavaScript framework for building user interfaces. It has a simple, yet very powerful API along with great performance. Vue.js is capable of powering the front-end layer and logic of any web application irrespective of the size. The ease of integrating it with other libraries or existing projects makes it a perfect choice for most modern web applications.

      In this tutorial, you’ll build a Nest.js application to get yourself familiar with its building blocks as well as the fundamental principles of building modern web applications. You’ll approach this project by separating the application into two different sections: the frontend and the backend. Firstly, you’ll concentrate on the RESTful back-end API built with Nest.js. You’ll then focus on the frontend, which you will build with Vue.js. Both applications will run on different ports and will function as separate domains.

      You’ll build a blog application with which users can create and save a new post, view the saved posts on the homepage, and carry out other processes such as editing and deleting posts. Furthermore, you’ll connect your application and persist its data with MongoDB, which is a schema-less NoSQL database that can receive and store JSON documents. This tutorial focuses on building your application in a development environment. For a production environment, you should also consider user authentication for your application.

      Prerequisites

      To complete this tutorial, you will need:

      Note: This tutorial uses a macOS machine for development. If you’re using another operating system, you may need to use sudo for npm commands throughout the tutorial.

      Step 1 — Installing Nest.js and Other Dependencies

      In this section, you will get started with Nest.js by installing the application and its required dependencies on your local machine. You can easily install Nest.js by either using the CLI that Nest.js provides, or, by installing the starter project from GitHub. For the purpose of this tutorial, you’ll use the CLI to set up the application. To begin, run the following command from the terminal to have it installed globally on your machine:

      You will see output similar to the following:

      Output

      @nestjs/cli@5.8.0 added 220 packages from 163 contributors in 49.104s

      To confirm your installation of the Nest CLI, run this command from your terminal:

      You'll see output showing the current version installed on your machine:

      Output

      5.8.0

      You'll make use of the nest command to manage your project and use it to generate relevant files — like the controller, modules, and providers.

      To begin the project for this tutorial, use the nest command to craft a new Nest.js project named blog-backend by running the following command from your terminal:

      Immediately after running the command, nest will prompt you to provide some basic information like the description, version, and author. Go ahead and provide the appropriate details. Hit ENTER on your computer to proceed after responding to each prompt.

      Next, you'll choose a package manager. For the purpose of this tutorial, select npm and hit ENTER to start installing Nest.js.

      Alt Creating a Nest project

      This will generate a new Nest.js project in a blog-backend folder within your local development folder.

      Next, navigate to the new project’s folder from your terminal:

      Run the following command to install other server dependencies:

      • npm install --save @nestjs/mongoose mongoose

      You've installed @nestjs/mongoose, which is a Nest.js dedicated package for an object modelling tool for MongoDB, and mongoose, which is a package for Mongoose.

      Now you'll start the application using the following command:

      Now, if you navigate to http://localhost:3000 from your favorite browser, you will see your application running.

      Alt Welcome page of the fresh installation of Nest.js application

      You've successfully generated the project by leveraging the availability of the Nest CLI command. Afterward, you proceeded to run the application and accessed it on the default port 3000 on your local machine. In the next section, you'll take the application further by setting up the configuration for the database connection.

      Step 2 — Configuring and Connecting with the Database

      In this step, you'll configure and integrate MongoDB into your Nest.js application. You'll use MongoDB to store data for your application. MongoDB stores its data in documents as field : value pairs. To access this data structure, you'll use Mongoose, which is an object document modeling (ODM) that allows you to define schemas representing the types of data that a MongoDB database stores.

      To start MongoDB, open a separate terminal window so that the application can keep running, and then execute the following command:

      This will start the MongoDB service and run the database in the background of your machine.

      Open the project blog-backend in your text editor and navigate to ./src/app.module.ts. You can set up a connection to the database by including the installed MongooseModule within the root ApplicationModule. To achieve this, update the content in app.module.ts with the following highlighted lines:

      ~/blog-backend/src/app.module.ts

      import { Module } from '@nestjs/common';
      import { AppController } from './app.controller';
      import { AppService } from './app.service';
      import { MongooseModule } from '@nestjs/mongoose';
      
      @Module({
        imports: [
          MongooseModule.forRoot('mongodb://localhost/nest-blog', { useNewUrlParser: true }),
        ],
        controllers: [AppController],
        providers: [AppService],
      })
      export class AppModule { }
      

      In this file, you use the forRoot() method to supply the connection to the database. Save and close the file when you are finished editing.

      With this in place, you have set up the database connection by using the Mongoose module for MongoDB. In the next section, you will create a database schema using the Mongoose library, a TypeScript interface, and a data transfer object (DTO) schema.

      Step 3 — Creating a Database Schema, Interfaces, and DTO

      In this step, you will create a schema, interface, and a data transfer object for your database using Mongoose. Mongoose helps to manage relationships between data and provides schema validation for data types. To help define the structure and datatype of the data in your application's database, you'll create files that determine the following:

      • database schema: This is an organization of data as a blueprint for defining the structure and the types of data that the database needs to store.

      • interfaces: TypeScript interfaces are used for type-checking. It can be used to define the types of data that should be passed for an application.

      • data transfer object: This is an object that defines how data will be sent over the network and carries the data between processes.

      To begin, go back to your terminal where the application is currently running and stop the process with CTRL + C, then navigate to the ./src/ folder:

      Then, create a directory named blog, and a schemas folder within that:

      In the schemas folder, create a new file called blog.schema.ts and open it using your text editor. Then, add the following content:

      ~/blog-backend/src/blog/schemas/blog.schema.ts

      import * as mongoose from 'mongoose';
      
      export const BlogSchema = new mongoose.Schema({
          title: String,
          description: String,
          body: String,
          author: String,
          date_posted: String
      })
      

      Here, you have used Mongoose to define the type of data that you will store in the database. You've specified that all the fields will store and only accept string values. Save and close the file when you are finished editing.

      Now, with the database schema determined, you can move on to creating the interfaces.

      To begin, navigate back into the blog folder:

      • cd ~/blog-backend/src/blog/

      Create a new folder named interfaces and move into it:

      In the interfaces folder, create a new file called post.interface.ts and open it using your text editor. Add the following content to define the types of data for a Post:

      ~/blog-backend/src/blog/interfaces/post.interface.ts

      import { Document } from 'mongoose';
      
      export interface Post extends Document {
          readonly title: string;
          readonly description: string;
          readonly body: string;
          readonly author: string;
          readonly date_posted: string
      }
      

      In this file, you have successfully defined the types of data for a Post type as string values. Save and exit the file.

      Since your application will carry out the functionality of posting data to the database, you will create a data transfer object that will define how data will be sent over the network.

      To achieve this, create a folder dto inside the ./src/blog folder. Within the newly created folder, create another file named create-post.dto.ts

      Navigate back into the blog folder:

      • cd ~/blog-backend/src/blog/

      Then create a folder named dto and move into it:

      In the dto folder, create a new file called create-post.dto.ts and open it using your text editor to add the following content:

      ~/blog-backend/src/blog/dto/create-post.dto.ts

      export class CreatePostDTO {
          readonly title: string;
          readonly description: string;
          readonly body: string;
          readonly author: string;
          readonly date_posted: string
      }
      

      You've marked each of the individual properties in the CreatePostDTO class to have a data type of string and as readonly to avoid unnecessary mutation. Save and exit the file when you are finished editing.

      In this step, you have created a database schema for the database, an interface, and a data transfer object for the data your database will store. Next, you'll generate a module, controller, and service for your blog.

      Step 4 — Creating the Module, Controller, and Service for the Blog

      In this step, you're going to improve on the existing structure of the application by creating a module for your blog. This module will organize the file structure of your application. Next, you'll create a controller to handle routes and process HTTP requests from the client. To wrap things up, you'll set up a service to handle all the business logic that is too complex for the controller of the application to process.

      Generating a Module

      Similarly to the Angular front-end web framework, Nest.js uses a modular syntax. Nest.js applications have a modular design; it comes installed with a single root module, which is often sufficient for a small application. But when an application starts to grow, Nest.js recommends a multiple-module organization, splitting the code into related features.

      A module in Nest.js is identified by the @Module() decorator and takes in an object with properties such as controllers and providers. Each of these properties takes an array of controllers and providers respectively.

      You will generate a new module for this blog application in order to keep the structure more organized. To begin, still in the ~/blog-backend folder, execute the following command:

      • nest generate module blog

      You will see output similar to the following:

      Output

      CREATE /src/blog/blog.module.ts UPDATE /src/app.module.ts

      The command generated a new module named blog.module.ts for the application and imported the newly created module into the root module for the application. This will allow Nest.js to be aware of another module besides the root module.

      In this file, you will see the following code:

      ~/blog-backend/src/blog/blog.module.ts

      import { Module } from '@nestjs/common';
      
      @Module({})
      export class BlogModule {}
      

      You will update this BlogModule with the required properties later in the tutorial. Save and exit the file.

      Generating a Service

      A service, which can also be called a provider in Nest.js, was designed to remove logic from controllers, which are meant to only handle HTTP requests and redirect more complex tasks to services. Services are plain JavaScript classes with an @Injectable() decorator on top of them. To generate a new service, run the following command from the terminal while you are still within the project directory:

      • nest generate service blog

      You will see output similar to the following:

      Output

      CREATE /src/blog/blog.service.spec.ts (445 bytes) CREATE /src/blog/blog.service.ts (88 bytes) UPDATE /src/blog/blog.module.ts (529 bytes)

      The nest command used here has created a blog.service.spec.ts file, which you can use for testing. It has also created a new blog.service.ts file, which will hold all the logic for this application and handle adding and retrieving documents to the MongoDB database. Also, it automatically imported the newly created service and added to blog.module.ts.

      The service handles all the logic within the application, is responsible for interacting with the database, and returns the appropriate responses back to the controller. To accomplish this, open the blog.service.ts file in your text editor and replace the contents with the following:

      ~/blog-backend/src/blog/blog.service.ts

      import { Injectable } from '@nestjs/common';
      import { Model } from 'mongoose';
      import { InjectModel } from '@nestjs/mongoose';
      import { Post } from './interfaces/post.interface';
      import { CreatePostDTO } from './dto/create-post.dto';
      
      @Injectable()
      export class BlogService {
      
          constructor(@InjectModel('Post') private readonly postModel: Model<Post>) { }
      
          async getPosts(): Promise<Post[]> {
              const posts = await this.postModel.find().exec();
              return posts;
          }
      
          async getPost(postID): Promise<Post> {
              const post = await this.postModel
                  .findById(postID)
                  .exec();
              return post;
          }
      
          async addPost(createPostDTO: CreatePostDTO): Promise<Post> {
              const newPost = await this.postModel(createPostDTO);
              return newPost.save();
          }
      
          async editPost(postID, createPostDTO: CreatePostDTO): Promise<Post> {
              const editedPost = await this.postModel
                  .findByIdAndUpdate(postID, createPostDTO, { new: true });
              return editedPost;
          }
      
          async deletePost(postID): Promise<any> {
              const deletedPost = await this.postModel
                  .findByIdAndRemove(postID);
              return deletedPost;
          }
      
      }
      
      

      In this file, you first imported the required module from @nestjs/common, mongoose, and @nestjs/mongoose. You also imported an interface named Post and a data transfer object CreatePostDTO.

      In the constructor, you added @InjectModel('Post'), which will inject the Post model into this BlogService class. You will now be able to use this injected model to retrieve all posts, fetch a single post, and carry out other database-related activities.

      Next, you created the following methods:

      • getPosts(): to fetch all posts from the database.
      • getPost(): to retrieve a single post from the database.
      • addPost(): to add a new post.
      • editPost(): to update a single post.
      • deletePost(): to delete a particular post.

      Save and exit the file when you are finished.

      You have finished setting up and creating several methods that will handle proper interaction with the MongoDB database from the back-end API. Now, you will create the required routes that will handle HTTP calls from a front-end client.

      Generating a Controller

      In Nest. js, controllers are responsible for handling any incoming requests from the client side of an application and returning the appropriate response. Similarly to most other web frameworks, it is important for the application to listen for a request and respond to it.

      To cater to all the HTTP requests for your blog application, you will leverage the nest command to generate a new controller file. Ensure that you are still in the project directory, blog-backend, and run the following command:

      • nest generate controller blog

      You will see output similar to:

      Output

      CREATE /src/blog/blog.controller.spec.ts (474 bytes) CREATE /src/blog/blog.controller.ts (97 bytes) UPDATE /src/blog/blog.module.ts (483 bytes)

      The output indicates that this command created two new files within the src/blog directory. They are blog.controller.spec.ts and blog.controller.ts. The former is a file that you can use to write automated testing for the newly created controller. The latter is the controller file itself. Controllers in Nest.js are TypeScript files decorated with @Controller metadata. The command also imported the newly created controller and added to the blog module.

      Next, open the blog.controller.ts file with your text editor and update it with the following content:

      ~/blog-backend/src/blog/blog.controller.ts

      import { Controller, Get, Res, HttpStatus, Param, NotFoundException, Post, Body, Query, Put, Delete } from '@nestjs/common';
      import { BlogService } from './blog.service';
      import { CreatePostDTO } from './dto/create-post.dto';
      import { ValidateObjectId } from '../shared/pipes/validate-object-id.pipes';
      
      
      @Controller('blog')
      export class BlogController {
      
          constructor(private blogService: BlogService) { }
      
          @Get('posts')
          async getPosts(@Res() res) {
              const posts = await this.blogService.getPosts();
              return res.status(HttpStatus.OK).json(posts);
          }
      
          @Get('post/:postID')
          async getPost(@Res() res, @Param('postID', new ValidateObjectId()) postID) {
              const post = await this.blogService.getPost(postID);
              if (!post) throw new NotFoundException('Post does not exist!');
              return res.status(HttpStatus.OK).json(post);
      
          }
      
          @Post('/post')
          async addPost(@Res() res, @Body() createPostDTO: CreatePostDTO) {
              const newPost = await this.blogService.addPost(createPostDTO);
              return res.status(HttpStatus.OK).json({
                  message: "Post has been submitted successfully!",
                  post: newPost
              })
          }
      }
      

      In this file, you first imported the necessary modules to handle HTTP requests from @nestjs/common module. Then, you imported three new modules which are: BlogService, CreatePostDTO, and ValidateObjectId. After that, you injected the BlogService into the controller via a constructor in order to gain access and make use of the functions that are already defined within the BlogService file. This is a pattern regarded as dependency injection used in Nest.js to increase efficiency and enhance the modularity of the application.

      Finally, you created the following asynchronous methods:

      • getPosts(): This method will carry out the functionality of receiving an HTTP GET request from the client to fetch all posts from the database and then return the appropriate response. It is decorated with a @Get('posts').

      • getPost(): This takes a postID as a parameter and fetches a single post from the database. In addition to the postID parameter passed to this method, you realized the addition of an extra method named ValidateObjectId(). This method implements the PipeTransform interface from Nest.js. Its purpose is to validate and ensure that the postID parameter can be found in the database. You will define this method in the next section.

      • addPost(): This method will handle a POST HTTP request to add a new post to the database.

      To be able to edit and delete a particular post, you will need to add two more methods to the blog.controller.ts file. To do that, include the following editPost() and deletePost() methods directly after the addPost() method you previously added to blog.controller.ts:

      ~/blog-backend/src/blog/blog.controller.ts

      
      ...
      @Controller('blog')
      export class BlogController {
          ...
          @Put('/edit')
          async editPost(
              @Res() res,
              @Query('postID', new ValidateObjectId()) postID,
              @Body() createPostDTO: CreatePostDTO
          ) {
              const editedPost = await this.blogService.editPost(postID, createPostDTO);
              if (!editedPost) throw new NotFoundException('Post does not exist!');
              return res.status(HttpStatus.OK).json({
                  message: 'Post has been successfully updated',
                  post: editedPost
              })
          }
      
      
          @Delete('/delete')
          async deletePost(@Res() res, @Query('postID', new ValidateObjectId()) postID) {
              const deletedPost = await this.blogService.deletePost(postID);
              if (!deletedPost) throw new NotFoundException('Post does not exist!');
              return res.status(HttpStatus.OK).json({
                  message: 'Post has been deleted!',
                  post: deletedPost
              })
          }
      }
      

      Here you have added:

      • editPost(): This method accepts a query parameter of postID and will carry out the functionality of updating a single post. It also made use of the ValidateObjectId method to provide proper validation for the post that you need to edit.

      • deletePost(): This method will accept a query parameter of postID and will delete a particular post from the database.

      Similarly to the BlogController, each of the asynchronous methods you have defined here has a metadata decorator and takes in a prefix that Nest.js uses as a routing mechanism. It controls which controller receives which requests and points to the methods that should process the request and return a response respectively.

      For example, the BlogController that you have created in this section has a prefix of blog and a method named getPosts() that takes in a prefix of posts. This means that any GET request sent to an endpoint of blog/posts (http:localhost:3000/blog/posts) will be handled by the getPosts()method. This example is similar to how other methods will handle HTTP requests.

      Save and exit the file.

      For the complete blog.controller.ts file, visit the DO Community repository for this application.

      In this section, you have created a module to keep the application more organized. You also created a service to handle the business logic for the application by interacting with the database and returning the appropriate response. Finally, you generated a controller and created the required methods to handle HTTP requests such as GET, POST, PUT, and DELETE from the client side. In the next step, you'll complete your back-end setup.

      You can identify each post in your blog application by a unique ID, also known as PostID. This means that fetching a post will require you to pass this ID as a query parameter. To validate this postID parameter and ensure that the post is available in the database, you need to create a reusable function that can be initialized from any method within the BlogController.

      To configure this, navigate to the ./src/blog folder:

      Then, create a new folder named shared:

      In the pipes folder, using your text editor, create a new file called validate-object-id.pipes.ts and open it. Add the following content to define the accepted postID data:

      ~/blog-backend/src/blog/shared/pipes/validate-object-id.pipes.ts

      import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';
      import * as mongoose from 'mongoose';
      
      @Injectable()
      export class ValidateObjectId implements PipeTransform<string> {
          async transform(value: string, metadata: ArgumentMetadata) {
              const isValid = mongoose.Types.ObjectId.isValid(value);
              if (!isValid) throw new BadRequestException('Invalid ID!');
              return value;
          }
      }
      

      The ValidateObjectId() class implements the PipeTransform method from the @nestjs/common module. It has a single method named transform() that takes in value as a parameter — postID in this case. With the method above, any HTTP request from the frontend of this application with a postID that can’t be found in the database will be regarded as invalid. Save and close the file.

      After creating both the service and controller, you need to set up the Post model that is based on the BlogSchema. This configuration could be set up within the root ApplicationModule, but in this instance building the model in BlogModule will maintain your application's organization. Open the ./src/blog/blog.module.ts and update it with the following highlighted lines:

      ~/blog-backend/src/blog/blog.module.ts

      import { Module } from '@nestjs/common';
      import { BlogController } from './blog.controller';
      import { BlogService } from './blog.service';
      import { MongooseModule } from '@nestjs/mongoose';
      import { BlogSchema } from './schemas/blog.schema';
      
      @Module({
        imports: [
          MongooseModule.forFeature([{ name: 'Post', schema: BlogSchema }])
       ],
        controllers: [BlogController],
        providers: [BlogService]
      })
      export class BlogModule { }
      

      This module uses the MongooseModule.forFeature() method to define which models should be registered in the module. Without this, injecting the PostModel within the BlogService using @injectModel() decorator wouldn't work. Save and close the file when you have finished adding the content.

      In this step, you've created the complete backend RESTful API with Nest.js and integrated it with MongoDB. In the next section, you'll configure the server to allow HTTP requests from another server, because your frontend application will be running on a different port.

      Step 6 — Enabling CORS

      An HTTP request from one domain to another is often blocked by default, except when specified by the server to allow it. For your front-end application to make a request to the back-end server, you must enable Cross-origin resource sharing (CORS), which is a technique that allows requests for restricted resources on a web page.

      In Nest.js to enable CORS, you need to add a single method to your main.ts file. Open this file in your text editor, which is located at ./src/main.ts, and update it with the following highlighted content:

      ~/blog-backend/src/main.ts

      import { NestFactory } from '@nestjs/core';
      import { AppModule } from './app.module';
      
      async function bootstrap() {
        const app = await NestFactory.create(AppModule);
        app.enableCors();
        await app.listen(3000);
      }
      bootstrap();
      
      

      Save and exit the file.

      Now that you have completed the back-end setup, you'll shift your focus to the frontend and use Vue.js to consume the APIs built so far.

      Step 7 — Creating the Vue.js Frontend

      In this section, you are going to create your front-end application with Vue.js. Vue CLI is a standard tool that allows you to quickly generate and install a new Vue.js project without much hassle.

      To begin, you first need to install the Vue CLI globally on your machine. Open another terminal, and instead of working from the blog-backend folder, navigate to your local project's development folder and run:

      Once the installation process is complete, you'll make use of the vue command to create a new Vue.js project:

      You'll see a short prompt after you've entered this command. Choose the manually select features option, and then select the features you'll need for this project by pressing SPACE on your computer to highlight multiple features. You'll select Babel, Router, and Linter / Formatter.

      Alt Vue project CLI set up

      For the next instructions, type y to use history mode for a router; this will ensure that history mode is enabled within the router file, which will automatically generate for this project. In addition, select ESLint with error prevention only to pick a linter/formatter configuration. Next, select Lint on save for additional Lint features. Then select to save your configuration in a dedicated config file for future projects. Type a name for your preset, like vueconfig.

      Alt Vue.js final CLI set up

      Vue.js will then start creating the application and all its required dependencies in a directory named blog-frontend.

      Once the installation process is complete, navigate inside the Vue.js application:

      Then, start the development server with:

      Your application will be running on http://localhost:8080.

      Alt Vue.js home view

      Since you'll be performing HTTP requests within this application, you'll need to install Axios, which is a promise-based HTTP client for the browser. You'll use Axios here to perform HTTP requests from the different components within the application. Stop the front-end application by hitting CTRL + C from the terminal on your computer and then run the following command:

      Your front-end application will be making an API call to the back-end API on a particular domain from different components within the application. In order to ensure proper structure for this application, you can create a helper file and define the server baseURL.

      To begin, from you terminal still within blog-frontend, navigate to the ./src/ folder:

      Create another folder named utils:

      In the utils folder, using your text editor, create a new file called helper.js and open it. Add the following content to define the baseURL for the back-end Nest.js project:

      ~blog-frontend/src/utils/helper.js

      export const server = {
      
      baseURL: 'http://localhost:3000'
      
      }
      

      By defining a baseURL, you'll be able to call it from anywhere within you Vue.js component files. In the event that you need to change the URL, it will be an easier process to update the baseURL in this file rather than across your application.

      In this section, you installed the Vue CLI, a tool for creating a new Vue.js application. You used this tool to craft the blog-frontend application. In addition, you ran the application and installed a library named Axios, which you will use whenever there is an HTTP call within the app. Next, you will create components for the application.

      Step 8 — Creating Reusable Components

      Now you're going to create reusable components for your application, which is the standard structure for Vue.js applications. The component system in Vue.js makes it possible for developers to build a single, independent unit of an interface that can have its own state, markup, and style. This makes it appropriate for components in Vue.js to be reusable.

      Every Vue.js component contains three different sections:

      • <template>: contains the HTML contents

      • <script>: holds all the basic frontend logic and defines the functions

      • <style>: the stylesheet for each separate component

      First, you'll start by creating a component to create a new post. To do that, create a new folder named post within the ./src/components folder, which will house the necessary reusable components for posts. Then using your text editor, inside the newly created post folder, create another file and name it Create.vue. Open the new file and add the following code, which contains the necessary input fields for submitting a post:

      ~blog-frontend/src/components/post/Create.vue

      <template>
         <div>
              <div class="col-md-12 form-wrapper">
                <h2> Create Post </h2>
                <form id="create-post-form" @submit.prevent="createPost">
                     <div class="form-group col-md-12">
                      <label for="title"> Title </label>
                      <input type="text" id="title" v-model="title" name="title" class="form-control" placeholder="Enter title">
                     </div>
                    <div class="form-group col-md-12">
                        <label for="description"> Description </label>
                        <input type="text" id="description" v-model="description" name="description" class="form-control" placeholder="Enter Description">
                    </div>
                    <div class="form-group col-md-12">
                        <label for="body"> Write Content </label>
                        <textarea id="body" cols="30" rows="5" v-model="body" class="form-control"></textarea>
                    </div>
                    <div class="form-group col-md-12">
                        <label for="author"> Author </label>
                        <input type="text" id="author" v-model="author" name="author" class="form-control">
                    </div>
      
                    <div class="form-group col-md-4 pull-right">
                        <button class="btn btn-success" type="submit"> Create Post </button>
                    </div>          
                </form>
              </div>
          </div>
      </template>
      

      This is the <template> section of the CreatePost component. It contains the HTML input elements required to create a new post. Each of the input fields has a v-model directive as an input attribute. This is to ensure two-way data bindings on each of the form input to make it easy for Vue.js to obtain the user's input.

      Next, add the <script> section to the same file directly following the preceding content:

      ~blog-frontend/src/components/post/Create.vue

      ...
      <script>
      import axios from "axios";
      import { server } from "../../utils/helper";
      import router from "../../router";
      export default {
        data() {
          return {
            title: "",
            description: "",
            body: "",
            author: "",
            date_posted: ""
          };
        },
        created() {
          this.date_posted = new Date().toLocaleDateString();
        },
        methods: {
          createPost() {
            let postData = {
              title: this.title,
              description: this.description,
              body: this.body,
              author: this.author,
              date_posted: this.date_posted
            };
            this.__submitToServer(postData);
          },
          __submitToServer(data) {
            axios.post(`${server.baseURL}/blog/post`, data).then(data => {
              router.push({ name: "home" });
            });
          }
        }
      };
      </script>
      
      

      Here you've added a method named createPost() to create a new post and submit it to the server using Axios. Once a user creates a new post, the application will redirect back to the homepage where users can view the list of created posts.

      You will configure vue-router to implement the redirection later in this tutorial.

      Save and close the file when you are finished editing. For the complete Create.vue file, visit the DO Community repository for this application.

      Now, you need to create another component for editing a particular post. Navigate to ./src/components/post folder and create another file and name it Edit.vue. Add the following code that contains the <template> section to it:

      ~blog-frontend/src/components/post/Edit.vue

      <template>
      <div>
            <h4 class="text-center mt-20">
             <small>
               <button class="btn btn-success" v-on:click="navigate()"> View All Posts </button>
             </small>
          </h4>
              <div class="col-md-12 form-wrapper">
                <h2> Edit Post </h2>
                <form id="edit-post-form" @submit.prevent="editPost">
                  <div class="form-group col-md-12">
                      <label for="title"> Title </label>
                      <input type="text" id="title" v-model="post.title" name="title" class="form-control" placeholder="Enter title">
                  </div>
                  <div class="form-group col-md-12">
                      <label for="description"> Description </label>
                      <input type="text" id="description" v-model="post.description" name="description" class="form-control" placeholder="Enter Description">
                  </div>
                  <div class="form-group col-md-12">
                      <label for="body"> Write Content </label>
                      <textarea id="body" cols="30" rows="5" v-model="post.body" class="form-control"></textarea>
                  </div>
                  <div class="form-group col-md-12">
                      <label for="author"> Author </label>
                      <input type="text" id="author" v-model="post.author" name="author" class="form-control">
                  </div>
      
                  <div class="form-group col-md-4 pull-right">
                      <button class="btn btn-success" type="submit"> Edit Post </button>
                  </div>
                </form>
              </div>
          </div>
      </template>
      
      

      This template section holds similar content as the CreatePost() component; the only difference is that it contains the details of the particular post that needs to be edited.

      Next, add the<script> section directly following the </template> section in Edit.vue:

      ~blog-frontend/src/components/post/Edit.vue

      ...
      <script>
      import { server } from "../../utils/helper";
      import axios from "axios";
      import router from "../../router";
      export default {
        data() {
          return {
            id: 0,
            post: {}
          };
        },
        created() {
          this.id = this.$route.params.id;
          this.getPost();
        },
        methods: {
          editPost() {
            let postData = {
              title: this.post.title,
              description: this.post.description,
              body: this.post.body,
              author: this.post.author,
              date_posted: this.post.date_posted
            };
      
            axios
              .put(`${server.baseURL}/blog/edit?postID=${this.id}`, postData)
              .then(data => {
                router.push({ name: "home" });
              });
          },
          getPost() {
            axios
              .get(`${server.baseURL}/blog/post/${this.id}`)
              .then(data => (this.post = data.data));
          },
          navigate() {
            router.go(-1);
          }
        }
      };
      </script>
      
      

      Here, you obtained the route parameter id to identify a particular post. You then created a method named getPost() to retrieve the details of this post from the database and updated the page with it. Finally, you created an editPost() method to submit the edited post back to the back-end server with a PUT HTTP request.

      Save and exit the file. For the complete Edit.vue file, visit the DO Community repository for this application.

      Now, you'll create a new component within the ./src/components/post folder and name it Post.vue. This will allow you to view the details of a particular post from the homepage. Add the following content to Post.vue:

      ~blog-frontend/src/components/post/Post.vue

      <template>
          <div class="text-center">
              <div class="col-sm-12">
            <h4 style="margin-top: 30px;"><small><button class="btn btn-success" v-on:click="navigate()"> View All Posts </button></small></h4>
            <hr>
            <h2>{{ post.title }}</h2>
            <h5><span class="glyphicon glyphicon-time"></span> Post by {{post.author}}, {{post.date_posted}}.</h5>
            <p> {{ post.body }} </p>
      
          </div>
          </div>
      </template>
      

      This code renders the details of a post that includes, title, author, and the post body.

      Now, directly following </template>, add the following code to the file:

      ~blog-frontend/src/components/post/Post.vue

      ...
      <script>
      import { server } from "../../utils/helper";
      import axios from "axios";
      import router from "../../router";
      export default {
        data() {
          return {
            id: 0,
            post: {}
          };
        },
        created() {
          this.id = this.$route.params.id;
          this.getPost();
        },
        methods: {
          getPost() {
            axios
              .get(`${server.baseURL}/blog/post/${this.id}`)
              .then(data => (this.post = data.data));
          },
          navigate() {
            router.go(-1);
          }
        }
      };
      </script>
      

      Similar to the <script> section of the edit post component, you obtained the route parameter id and used it to retrieve the details of a particular post.

      Save and close the file when you are finished adding the content. For the complete Post.vue file, visit the DO Community repository for this application.

      Next, to display all the created posts to users, you will create a new component. If you navigate to the views folder in src/views, you will see a Home.vue component — if this file is not present, use your text editor to create it, add the following code:

      ~blog-frontend/src/views/Home.vue

      <template>
          <div>
      
            <div class="text-center">
              <h1>Nest Blog Tutorial</h1>
             <p> This is the description of the blog built with Nest.js, Vue.js and MongoDB</p>
      
             <div v-if="posts.length === 0">
                  <h2> No post found at the moment </h2>
              </div>
            </div>
      
              <div class="row">
                 <div class="col-md-4" v-for="post in posts" :key="post._id">
                    <div class="card mb-4 shadow-sm">
                      <div class="card-body">
                         <h2 class="card-img-top">{{ post.title }}</h2>
                        <p class="card-text">{{ post.body }}</p>
                        <div class="d-flex justify-content-between align-items-center">
                          <div class="btn-group" style="margin-bottom: 20px;">
                            <router-link :to="{name: 'Post', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">View Post </router-link>
                             <router-link :to="{name: 'Edit', params: {id: post._id}}" class="btn btn-sm btn-outline-secondary">Edit Post </router-link>
                             <button class="btn btn-sm btn-outline-secondary" v-on:click="deletePost(post._id)">Delete Post</button>
                          </div>
                        </div>
      
                        <div class="card-footer">
                          <small class="text-muted">Posted on: {{ post.date_posted}}</small><br/>
                          <small class="text-muted">by: {{ post.author}}</small>
                        </div>
      
                      </div>
                    </div>
                  </div>
            </div>
          </div>
      </template>
      
      

      Here, within the <template> section, you used the <router-link> to create a link for editing as well as for viewing a post by passing the post._id as a query parameter. You also used the v-if directive to conditionally render the post for users. If there is no post from the database, a user will only see this text: No post found at the moment.

      Save and exit the file. For the complete Home.vue file, visit the DO Community repository for this application.

      Now, directly following the </template> section in Home.vue, add the following </script> section:

      ~blog-frontend/src/views/Home.vue

      ...
      <script>
      // @ is an alias to /src
      import { server } from "@/utils/helper";
      import axios from "axios";
      
      export default {
        data() {
          return {
            posts: []
          };
        },
        created() {
          this.fetchPosts();
        },
        methods: {
          fetchPosts() {
            axios
              .get(`${server.baseURL}/blog/posts`)
              .then(data => (this.posts = data.data));
          },
          deletePost(id) {
            axios.delete(`${server.baseURL}/blog/delete?postID=${id}`).then(data => {
              console.log(data);
              window.location.reload();
            });
          }
        }
      };
      </script>
      
      

      Within the <script> section of this file, you created a method named fetchPosts() to fetch all posts from the database, and you updated the page with the data returned from the server.

      Now, you'll update the App component of the front-end application in order to create links to the Home and Create components. Open src/App.vue and update it with the following:

      ~blog-frontend/src/App.vue

      <template>
        <div id="app">
          <div id="nav">
            <router-link to="/">Home</router-link> |
            <router-link to="/create">Create</router-link>
          </div>
          <router-view/>
        </div>
      </template>
      
      <style>
      #app {
        font-family: "Avenir", Helvetica, Arial, sans-serif;
        -webkit-font-smoothing: antialiased;
        -moz-osx-font-smoothing: grayscale;
        color: #2c3e50;
      }
      #nav {
        padding: 30px;
        text-align: center;
      }
      
      #nav a {
        font-weight: bold;
        color: #2c3e50;
      }
      
      #nav a.router-link-exact-active {
        color: #42b983;
      }
      </style>
      

      Apart from including the links to both Home and Create components, you also included the <Style> section, which is the stylesheet for this component and holds the definition of styles for some of the elements on the page. Save and exit the file.

      You have created all the required components for your application in this step. Next, you will configure the router file.

      Step 9 — Setting Up Routing

      After creating all the necessary reusable components, you can now properly configure the router file by updating its content with links to all the components you've created. This will ensure that all endpoints within the front-end application are mapped to a particular component for appropriate action. Navigate to ./src/router.js and replace its content with the following:

      ~blog-frontend/src/router.js

      import Vue from 'vue'
      import Router from 'vue-router'
      import HomeComponent from '@/views/Home';
      import EditComponent from '@/components/post/Edit';
      import CreateComponent from '@/components/post/Create';
      import PostComponent from '@/components/post/Post';
      
      Vue.use(Router)
      
      export default new Router({
        mode: 'history',
        routes: [
          { path: '/', redirect: { name: 'home' } },
          { path: '/home', name: 'home', component: HomeComponent },
          { path: '/create', name: 'Create', component: CreateComponent },
          { path: '/edit/:id', name: 'Edit', component: EditComponent },
          { path: '/post/:id', name: 'Post', component: PostComponent }
        ]
      });
      

      You imported Router from the vue-router module and instantiated it by passing the mode and routes parameters. The default mode for vue-router is a hash mode, which uses the URL hash to simulate a full URL so that the page won't be reloaded when the URL changes. In order to make the hash unnecessary, you have used history mode here to achieve URL navigation without a page reload. Finally, within the routes option, you specified the path for the endpoint — a name for the route and the component that should be rendered when the route is called within the application. Save and exit the file.

      Now that you have set up routing to the application, you need to include the Bootstrap file to help with pre-built styling for the user interface of the application. To achieve that, open ./public/index.html file in your text editor and include the CDN file for Bootstrap by adding the following content to the file:

      ~blog-frontend/public/index.html

      <!DOCTYPE html>
      <html lang="en">
      <head>
        ...
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css">
        <title>blog-frontend</title>
      </head>
      <body>
         ...
      </body>
      </html>
      

      Save and exit the file, and then restart the application with npm run serve for your blog-frontend, if it is not currently running.

      Note: Ensure that both the back-end server and the MongoDB instance are running as well. If otherwise, navigate to the blog-backend from another terminal and run npm run start. Also, start the MongoDB service by running sudo mongod from a new terminal as well.

      Navigate to your application at: http://localhost:8080. Now you can test your blog by creating and editing posts.

      Alt Create a new post

      Click on Create on your application to see the Create Post screen, which relates to and renders the CreateComponent file. Enter values into the input fields and click on the Create Post button to submit a post. Once you are done, the application will redirect you back to the homepage.

      The homepage of the application renders the HomeComponent. This component has a method that sends an HTTP call to fetch all posts from the database and displays them to users.

      Alt View all posts from the database

      Clicking on the Edit Post button for a particular post will take you to an edit page where you can incorporate any changes and save your post.

      Alt Edit a new post

      In this section, you configured and set up routing for the application. With this in place, your blog application is ready.

      Conclusion

      In this tutorial, you have explored a new way of structuring a Node.js application by using Nest.js. You created a simple blog application using Nest.js to build the back-end RESTful API and used Vue.js to handle all the front-end logic. Furthermore, you also integrated MongoDB as a database for your Nest.js application.

      To learn more about how to add authentication to your application, you can make use of Passport.js, a popular Node.js authentication library. You can learn about Passport.js integration in the Nest.js documentation.

      You can find the complete source code for this project here on GitHub. For more information about Nest.js, you can visit the official documentation.



      Source link

      How To Integrate MongoDB with Your Node Application


      Introduction

      As you work with Node.js, you may find yourself developing a project that stores and queries data. In this case, you will need to choose a database solution that makes sense for your application’s data and query types.

      In this tutorial, you will integrate a MongoDB database with an existing Node application. NoSQL databases like MongoDB can be useful if your data requirements include scalability and flexibility. MongoDB also integrates well with Node since it is designed to work asynchronously with JSON objects.

      To integrate MongoDB into your project, you will use the Object Document Mapper (ODM) Mongoose to create schemas and models for your application data. This will allow you to organize your application code following the model-view-controller (MVC) architectural pattern, which lets you separate the logic of how your application handles user input from how your data is structured and rendered to the user. Using this pattern can facilitate future testing and development by introducing a separation of concerns into your codebase.

      At the end of the tutorial, you will have a working shark information application that will take a user’s input about their favorite sharks and display the results in the browser:

      Shark Output

      Prerequisites

      Step 1 — Creating a Mongo User

      Before we begin working with the application code, we will create an administrative user that will have access to our application’s database. This user will have administrative privileges on any database, which will give you the flexibility to switch and create new databases as needed.

      First, check that MongoDB is running on your server:

      • sudo systemctl status mongodb

      The following output indicates that MongoDB is running:

      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 ...

      Next, open the Mongo shell to create your user:

      This will drop you into an administrative shell:

      Output

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

      You will see some administrative warnings when you open the shell due to your unrestricted access to the admin database. You can learn more about restricting this access by reading How To Install and Secure MongoDB on Ubuntu 16.04, for when you move into a production setup.

      For now, you can use your access to the admin database to create a user with userAdminAnyDatabase privileges, which will allow password-protected access to your application's databases.

      In the shell, specify that you want to use the admin database to create your user:

      Next, create a role and password by adding a username and password with the db.createUser command. After you type this command, the shell will prepend three dots before each line until the command is complete. Be sure to replace the user and password provided here with your own username and password:

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

      This creates an entry for the user sammy in the admin database. The username you select and the admin database will serve as identifiers for your user.

      The output for the entire process will look like this, including the message indicating that the entry was successful:

      Output

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

      With your user and password created, you can now exit the Mongo shell:

      Now that you have created your database user, you can move on to cloning the starter project code and adding the Mongoose library, which will allow you to implement schemas and models for the collections in your databases.

      Step 2 — Adding Mongoose and Database Information to the Project

      Our next steps will be to clone the application starter code and add Mongoose and our MongoDB database information to the project.

      In your non-root user's home directory, clone the nodejs-image-demo repository from the DigitalOcean Community GitHub account. This repository includes the code from the setup described in How To Build a Node.js Application with Docker.

      Clone the repository into a directory called node_project:

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

      Change to the node_project directory:

      Before modifying the project code, let's take a look at the project's structure using the tree command.

      Tip: tree is a useful command for viewing file and directory structures from the command line. You can install it with the following command:

      To use it, cd into a given directory and type tree. You can also provide the path to the starting point with a command like:

      • tree /home/sammy/sammys-project

      Type the following to look at the node_project directory:

      The structure of the current project looks like this:

      Output

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

      We will be adding directories to this project as we move through the tutorial, and tree will be a useful command to help us track our progress.

      Next, add the mongoose npm package to the project with the npm install command:

      This command will create a node_modules directory in your project directory, using the dependencies listed in the project's package.json file, and will add mongoose to that directory. It will also add mongoose to the dependencies listed in your package.json file. For a more detailed discussion of package.json, please see Step 1 in How To Build a Node.js Application with Docker.

      Before creating any Mongoose schemas or models, we will add our database connection information so that our application will be able to connect to our database.

      In order to separate your application's concerns as much as possible, create a separate file for your database connection information called db.js. You can open this file with nano or your favorite editor:

      First, import the mongoose module using the require function:

      ~/node_project/db.js

      const mongoose = require('mongoose');
      

      This will give you access to Mongoose's built-in methods, which you will use to create the connection to your database.

      Next, add the following constants to define information for Mongo's connection URI. Though the username and password are optional, we will include them so that we can require authentication for our database. Be sure to replace the username and password listed below with your own information, and feel free to call the database something other than 'sharkinfo' if you would prefer:

      ~/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';
      

      Because we are running our database locally, we have used 127.0.0.1 as the hostname. This would change in other development contexts: for example, if you are using a separate database server or working with multiple nodes in a containerized workflow.

      Finally, define a constant for the URI and create the connection using the mongoose.connect() method:

      ~/node_project/db.js

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

      Note that in the URI we've specified the authSource for our user as the admin database. This is necessary since we have specified a username in our connection string. Using the useNewUrlParser flag with mongoose.connect() specifies that we want to use Mongo's new URL parser.

      Save and close the file when you are finished editing.

      As a final step, add the database connection information to the app.js file so that the application can use it. Open app.js:

      The first lines of the file will look like this:

      ~/node_project/app.js

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

      Below the router constant definition, located near the top of the file, add the following line:

      ~/node_project/app.js

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

      This tells the application to use the database connection information specified in db.js.

      Save and close the file when you are finished editing.

      With your database information in place and Mongoose added to your project, you are ready to create the schemas and models that will shape the data in your sharks collection.

      Step 3 — Creating Mongoose Schemas and Models

      Our next step will be to think about the structure of the sharks collection that users will be creating in the sharkinfo database with their input. What structure do we want these created documents to have? The shark information page of our current application includes some details about different sharks and their behaviors:

      Shark Info Page

      In keeping with this theme, we can have users add new sharks with details about their overall character. This goal will shape how we create our schema.

      To keep your schemas and models distinct from the other parts of your application, create a models directory in the current project directory:

      Next, open a file called sharks.js to create your schema and model:

      Import the mongoose module at the top of the file:

      ~/node_project/models/sharks.js

      const mongoose = require('mongoose');
      

      Below this, define a Schema object to use as the basis for your shark schema:

      ~/node_project/models/sharks.js

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

      You can now define the fields you would like to include in your schema. Because we want to create a collection with individual sharks and information about their behaviors, let's include a name key and a character key. Add the following Shark schema below your constant definitions:

      ~/node_project/models/sharks.js

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

      This definition includes information about the type of input we expect from users — in this case, a string — and whether or not that input is required.

      Finally, create the Shark model using Mongoose's model() function. This model will allow you to query documents from your collection and validate new documents. Add the following line at the bottom of the file:

      ~/node_project/models/sharks.js

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

      This last line makes our Shark model available as a module using the module.exports property. This property defines the values that the module will export, making them available for use elsewhere in the application.

      The finished models/sharks.js file looks like this:

      ~/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)
      

      Save and close the file when you are finished editing.

      With the Shark schema and model in place, you can start working on the logic that will determine how your application will handle user input.

      Step 4 — Creating Controllers

      Our next step will be to create the controller component that will determine how user input gets saved to our database and returned to the user.

      First, create a directory for the controller:

      Next, open a file in that folder called sharks.js:

      • nano controllers/sharks.js

      At the top of the file, we'll import the module with our Shark model so that we can use it in our controller's logic. We'll also import the path module to access utilities that will allow us to set the path to the form where users will input their sharks.

      Add the following require functions to the beginning of the file:

      ~/node_project/controllers/sharks.js

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

      Next, we'll write a sequence of functions that we will export with the controller module using Node's exports shortcut. These functions will include the three tasks related to our user's shark data:

      • Sending users the shark input form.
      • Creating a new shark entry.
      • Displaying the sharks back to users.

      To begin, create an index function to display the sharks page with the input form. Add this function below your imports:

      ~/node_project/controllers/sharks.js

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

      Next, below the index function, add a function called create to make a new shark entry in your sharks collection:

      ~/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');
              }
        });
                     };
      

      This function will be called when a user posts shark data to the form on the sharks.html page. We will create the route with this POST endpoint later in the tutorial when we create our application's routes. With the body of the POST request, our create function will make a new shark document object, here called newShark, using the Shark model that we've imported. We've added a console.log method to output the shark entry to the console in order to check that our POST method is working as intended, but you should feel free to omit this if you would prefer.

      Using the newShark object, the create function will then call Mongoose's model.save() method to make a new shark document using the keys you defined in the Shark model. This callback function follows the standard Node callback pattern: callback(error, results). In the case of an error, we will send a message reporting the error to our users, and in the case of success, we will use the res.redirect() method to send users to the endpoint that will render their shark information back to them in the browser.

      Finally, the list function will display the collection's contents back to the user. Add the following code below the create function:

      ~/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
                   });
              });
      };
      

      This function uses the Shark model with Mongoose's model.find() method to return the sharks that have been entered into the sharks collection. It does this by returning the query object — in this case, all of the entries in the sharks collection — as a promise, using Mongoose's exec() function. In the case of an error, the callback function will send a 500 error.

      The returned query object with the sharks collection will be rendered in a getshark page that we will create in the next step using the EJS templating language.

      The finished file will look like this:

      ~/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
                   });
              });
      };
      

      Keep in mind that though we are not using arrow functions here, you may wish to include them as you iterate on this code in your own development process.

      Save and close the file when you are finished editing.

      Before moving on to the next step, you can run tree again from your node_project directory to view the project's structure at this point. This time, for the sake of brevity, we'll tell tree to omit the node_modules directory using the -I option:

      With the additions you've made, your project's structure will look like this:

      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

      Now that you have a controller component to direct how user input gets saved and returned to the user, you can move on to creating the views that will implement your controller's logic.

      Step 5 — Using EJS and Express Middleware to Collect and Render Data

      To enable our application to work with user data, we will do two things: first, we will include a built-in Express middleware function, urlencoded(), that will enable our application to parse our user's entered data. Second, we will add template tags to our views to enable dynamic interaction with user data in our code.

      To work with Express's urlencoded() function, first open your app.js file:

      Above your express.static() function, add the following line:

      ~/node_project/app.js

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

      Adding this function will enable access to the parsed POST data from our shark information form. We are specifying true with the extended option to enable greater flexibility in the type of data our application will parse (including things like nested objects). Please see the function documentation for more information about options.

      Save and close the file when you are finished editing.

      Next, we will add template functionality to our views. First, install the ejs package with npm install:

      Next, open the sharks.html file in the views folder:

      In Step 3, we looked at this page to determine how we should write our Mongoose schema and model:

      Shark Info Page

      Now, rather than having a two column layout, we will introduce a third column with a form where users can input information about sharks.

      As a first step, change the dimensions of the existing columns to 4 to create three equal-sized columns. Note that you will need to make this change on the two lines that currently read <div class="col-lg-6">. These will both become <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> 
      

      For an introduction to Bootstrap's grid system, including its row and column layouts, please see this introduction to Bootstrap.

      Next, add another column that includes the named endpoint for the POST request with the user's shark data and the EJS template tags that will capture that data. This column will go below the closing </p> and </div> tags from the preceding column and above the closing tags for the row, container, and HTML document. These closing tags are already in place in your code; they are also marked below with comments. Leave them in place as you add the following code to create the new column:

      ~/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 -->
      

      In the form tag, you are adding a "/sharks/addshark" endpoint for the user's shark data and specifying the POST method to submit it. In the input fields, you are specifying fields for "Shark Name" and "Shark Character", aligning with the Shark model you defined earlier.

      To add the user input to your sharks collection, you are using EJS template tags (<%=, %>) along with JavaScript syntax to map the user's entries to the appropriate fields in the newly created document. For more about JavaScript objects, please see our article on Understanding JavaScript Objects. For more on EJS template tags, please see the EJS documentation.

      The entire container with all three columns, including the column with your shark input form, will look like this when finished:

      ~/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>
      

      Save and close the file when you are finished editing.

      Now that you have a way to collect your user's input, you can create an endpoint to display the returned sharks and their associated character information.

      Copy the newly modified sharks.html file to a file called getshark.html:

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

      Open getshark.html:

      Inside the file, we will modify the column that we used to create our sharks input form by replacing it with a column that will display the sharks in our sharks collection. Again, your code will go between the existing </p> and </div> tags from the preceding column and the closing tags for the row, container, and HTML document. Remember to leave these tags in place as you add the following code to create the column:

      ~/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 -->
      

      Here you are using EJS template tags and the forEach() method to output each value in your sharks collection, including information about the most recently added shark.

      The entire container with all three columns, including the column with your sharks collection, will look like this when finished:

      ~/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>
      

      Save and close the file when you are finished editing.

      In order for the application to use the templates you've created, you will need to add a few lines to your app.js file. Open it again:

      Above where you added the express.urlencoded() function, add the following lines:

      ~/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));
      
      ...
      

      The app.engine method tells the application to map the EJS template engine to HTML files, while app.set defines the default view engine.

      Your app.js file should now look like this:

      ~/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!')
      })
      

      Now that you have created views that can work dynamically with user data, it's time to create your project's routes to bring together your views and controller logic.

      Step 6 — Creating Routes

      The final step in bringing the application's components together will be creating routes. We will separate our routes by function, including a route to our application's landing page and another route to our sharks page. Our sharks route will be where we integrate our controller's logic with the views we created in the previous step.

      First, create a routes directory:

      Next, open a file called index.js in this directory:

      This file will first import the express, router, and path objects, allowing us to define the routes we want to export with the router object, and making it possible to work dynamically with file paths. Add the following code at the top of the file:

      ~/node_project/routes/index.js

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

      Next, add the following router.use function, which loads a middleware function that will log the router's requests and pass them on to the application's route:

      ~/node_project/routes/index.js

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

      Requests to our application's root will be directed here first, and from here users will be directed to our application's landing page, the route we will define next. Add the following code below the router.use function to define the route to the landing page:

      ~/node_project/routes/index.js

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

      When users visit our application, the first place we want to send them is to the index.html landing page that we have in our views directory.

      Finally, to make these routes accessible as importable modules elsewhere in the application, add a closing expression to the end of the file to export the router object:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      The finished file will look like this:

      ~/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;
      

      Save and close this file when you are finished editing.

      Next, open a file called sharks.js to define how the application should use the different endpoints and views we've created to work with our user's shark input:

      At the top of the file, import the express and router objects:

      ~/node_project/routes/sharks.js

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

      Next, import a module called shark that will allow you to work with the exported functions you defined with your controller:

      ~/node_project/routes/sharks.js

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

      Now you can create routes using the index, create, and list functions you defined in your sharks controller file. Each route will be associated with the appropriate HTTP method: GET in the case of rendering the main sharks information landing page and returning the list of sharks to the user, and POST in the case of creating a new shark entry:

      ~/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);
      });
      

      Each route makes use of the related function in controllers/sharks.js, since we have made that module accessible by importing it at the top of this file.

      Finally, close the file by attaching these routes to the router object and exporting them:

      ~/node_project/routes/index.js

      ...
      
      module.exports = router;
      

      The finished file will look like this:

      ~/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;
      

      Save and close the file when you are finished editing.

      The last step in making these routes accessible to your application will be to add them to app.js. Open that file again:

      Below your db constant, add the following import for your routes:

      ~/node_project/app.js

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

      Next, replace the app.use function that currently mounts your router object with the following line, which will mount the sharks router module:

      ~/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!")
      })
      

      You can now delete the routes that were previously defined in this file, since you are importing your application's routes using the sharks router module.

      The final version of your app.js file will look like this:

      ~/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!')
      })
      

      Save and close the file when you are finished editing.

      You can now run tree again to see the final structure of your project:

      Your project structure will now look like this:

      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

      With all of your application components created and in place, you are now ready to add a test shark to your database!

      If you followed the initial server setup tutorial in the prerequisites, you will need to modify your firewall, since it currently only allows SSH traffic. To permit traffic to port 8080 run:

      Start the application:

      Next, navigate your browser to http://your_server_ip:8080. You will see the following landing page:

      Application Landing Page

      Click on the Get Shark Info button. You will see the following information page, with the shark input form added:

      Shark Info Form

      In the form, add a shark of your choosing. For the purpose of this demonstration, we will add Megalodon Shark to the Shark Name field, and Ancient to the Shark Character field:

      Filled Shark Form

      Click on the Submit button. You will see a page with this shark information displayed back to you:

      Shark Output

      You will also see output in your console indicating that the shark has been added to your collection:

      Output

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

      If you would like to create a new shark entry, head back to the Sharks page and repeat the process of adding a shark.

      You now have a working shark information application that allows users to add information about their favorite sharks.

      Conclusion

      In this tutorial, you built out a Node application by integrating a MongoDB database and rewriting the application's logic using the MVC architectural pattern. This application can act as a good starting point for a fully-fledged CRUD application.

      For more resources on the MVC pattern in other contexts, please see our Django Development series or How To Build a Modern Web Application to Manage Customer Information with Django and React on Ubuntu 18.04.

      For more information on working with MongoDB, please see our library of tutorials on MongoDB.



      Source link