<-- /notes<-- /notes/learn-node

MongoDB and ENV

In order to set up a MongoDB database (or any sort of database for that matter), it's best practice to create a .env file, which contains the app's sensitive information

DATABASE=mongodb://user:pass@host.com:port/database
PORT=7777
MAP_KEY=456123
SECRET=twizzlers

The dotenv node package is used to reference the .env file. It stores the data for access as a process.env object (created by calling require('dotenv').config({path: filename.env})).

The template for the database connection is used in mongoDB specifically in order to connect. For example, a localhost example (without a username/password), connecting to the admin database on port 27017 would be:

DATABASE=mongodb://localhost:27017/admin

Another important note, if you are setting up MongoDB on your local machine (instead of using a DB as a Service), and it happens to be running Windows, you will have to create a C:\data\db directory in order setup. To actually make it run, navigate to C:\Program Files\MongoDB\Server\versionNumber\bin and run mongod in a terminal. Congrats, you've spun up your database. 😃😃


Routing

Routing is the way of sending our app/user different sets of data based on the URL path. For this project, express.Router() is used for this.

The router let's us specify the file path, and the type of request, followed by what to do with it. See the following:

const router = express.Router()
// Perform this function for GET requests on homepage
router.get("/", (req, res, next) => {
  // req -> request object
  // res -> response object
  // next -> operation for middleware (more on this later)
  // req.query -> references localhost:PORT/?key1=value1&key2=value2
  // res.send() -> sends page data, ex: <p>Hello World!</p>
  // res.json() -> send JSON object (for API calls)
  const me = {
    name: "leander",
    age: 19,
    other: req.query.other,
  }
  res.json(me)
})
// Use the controller's addPage function to control the POST requests on localhost:PORT/add
router.post("/add", storeController.addStore)

Templating, Helpers and Mixins

Templates are a useful way to write reusable markup. For this app, the templating language used is pug (or formerly known as jade). which was chosen for its seamless integration with js.

The syntax of the templating language depends on which is chosen, but regardless, the core concept is the same; create a page which can accept data to dynamically create a new page.

// Use pug as the template engine
app.set("view engine", "pug");

// This is a controller, which is helpful for MVC programming. More on this later...
exports.addStore = (req, res) => {
  // The response renders a template 'editStore.pug', and passes the template data
  res.render("editStore", { title: "Hello World!" });
};

/*  ---- Within editStore.pug ----  */

extends layout // Use the layout.pug template

block content // Change the 'content' block of layout.pug to the following markup
  // Reference the supplied parameters
  h2 #{title}
  h3= title

Helpers are another useful way to write templates. They allow you to perform some basic functionality/operations on the incoming data for each template. Simple things like loops over arrays, key-value destructuring, comparators, and conditionals are all available for most templating languages.

In order to create your own helpers, simple put them in a helpers.js file as:

exports.example = "example value"
exports.dump = obj => JSON.stringify(obj, null, 2)

or something of the sort. Then, using middleware, they can be used in Express:

// Import the file
const helpers = require("./helpers.js");

// Add it as a helper
app.use((req, res, next) => {
  res.locals.h = helpers;
  next();
});

// Use it throughout the app (ex. pug)
h1 This is how a helper is used: #{h.example}

Mixins are a way of extending the modularity even further, essentially creating reusable components for multiple templates. For example, if your updateStore.pug template, and addStore.pug template both present the same form, with the same parameters, a _storeForm.pug mixin is probably a good idea.

Note: To distinguish mixins, it is recommended to store them in a separate directory, with filenames starting with an underscore.

In pug, mixins can be referenced and used as follows:

extends layout

include mixins/_storeForm

block content
  h1 Preceding data
  +storeForm({store: 'Cool Cats'})

<!-- Within _storeForm.pug -->
//- Declared similar to JS functions
mixin storeForm(store = '')
  //- Your template with its param values
  h1= store

Take advantage of templating, it's a useful tool!


MVC and Controllers

MVC programming is a code architecture concept. It stands for Model, View, Controller. It outlines a way of developing applications to process logic and display content to end users by separating the code into three distinct areas.

The model refers to the code layer which performs operations on the backend. This can be the seen as the requests for fetching/querying the database. In an analogy, the model is the pizza place.

The view is what the user sees. We think of the view as the templates and display. For the purpose of this application, these are the .pug files. We must display all of our information back to the user through the view. In an analogy, the view is your apartment.

The controller is the bridge between the model and the view. It gets data from the model, then passes it on to the view. In an analogy, the controller is the delivery driver.

//Analogy

By keeping the controllers separate from the model and view, we allow for a modular application, with reusable bits of code. We can create a controller by creating a separate file and exporting its functionality:

/*  ---- Model ----  */
const storeController = require("./storeController")

router.get("/", storeController.homepage)
router.get("/add", storeController.addStore)

/*  ---- View ----  */
index.pug
editStore.pug

/*  ---- Controller ----  */
exports.homePage = (req, res) => {
  res.render("index")
}

exports.addStore = (req, res) => {
  res.render("editStore", { ...params })
}

Middleware and Error Handling

Middleware is a term used to refer to the processing or functionality that goes on after the request, but before the response. Middleware is the main way of performing bulk operations through express, and it's passed simply as just another parameter to a normal controller function. Take the following code as an example.

// Within the exampleController.js file
exports.myMiddleware = (req, res, next) => {
  req.myMiddleWareVariable = "Example"
  console.log("This is an")
  next()
}

exports.myResponse = (req, res, next) => {
  console.log(req.myMiddleWareVariable)
  res.send("Finished")
}

// Then we would call upon the middleware as shown:
// router.get(route, controller)
router.get(
  "/example",
  exampleController.myMiddleWare,
  exampleController.myResponse
)

// When navigating to the route /example
//  -> console: This is an
//  -> console: Example
//  -> page: Finished

That is an example of route-specific middleware, but Express also has the capability for global middleware. This allows every single request to pass through the same middleware, enabling application-wide functionality.

This is called through the function app.use().

// app.use(middlewareFunction)
app.use(setTheseVariables)
app.use(gatherTheseStaticAssets)
app.use(makeThisDBFolder)

We can use middleware for error handling as well. If we queue up a bunch of functions to run before a certain operation, we can handle the edge cases on returns. For example:

// All routes starting at /admin use the adminRoutes router
const adminRoutes = require("./adminRoutes")
// app.use("/", routes);
app.use("/admin", adminRoutes)
app.use(errorHandlers.notFound)

Since that line is commented, if we navigate to anything other than localhost:PORT/admin, we will run into the fallback error handler: errorHandlers.notFound.