{{announcement.body}}
{{announcement.title}}

Create Routes With Node and Express

DZone 's Guide to

Create Routes With Node and Express

Routes and authentication with Node, Express, and Okta.

· Web Dev Zone ·
Free Resource

There’s a good chance that if you’re a web developer, you’ve used Express in the past. Express is known as a minimalist web framework for Node.js. A key feature of the framework is creating routes. Multiple URLs can use the same Express server and routes to determine what URLs can run certain pieces of code. Within the routes, you can set up rules or logic so you don’t have to manually state each endpoint. 

Today, I’ll teach you everything you need to know about creating routes with Node and  Express. 

What Is a Route in Express?

Routes determine what data should be delivered given any URL. Let’s take the most basic file server as an example. Say you have a file structure of:

files/
├── images/
│   ├── cat.png
│   ├── dog.jpg
│   └── pig.bmp
└── text/
    ├── README.md
    └── todo.txt


You could then run a simple HTTP server that will automatically serve those files and create an index for the directories. There’s no files/index.html, but the server is still generating a web page and serving up content based on the files in that folder. If you go to /images/cow.gif, you’ll get a 404 error — even though there’s no file there, it’s still serving up something.

npm install -g http-server
cd files
http-server


File Server Demo


In Express, a route consists of a method, a path, and a handler.

Methods, Paths, and Handlers, Oh My!

The method could be any HTTP verb, such as GET (for fetching content — this is what most web pages use), or POST (for sending content to the server — this is common with HTML forms). You can also specify that you want Express to handle the same path for all methods if you choose.

The path is a string or a regular expression that describes the relative URL. If you’re working with the root of your app, this describes the absolute URL. A path can be defined in a few ways.

  • Simple Strings: A string of '/' specifies that you want to use this route at the root of your router. A string of '/asdf'would cover the path /asdf
  • Wildcards: The string can also contain a few wildcards, which work similar to a regular expression, but are a bit limited:
    • ?: A ? says that the previous character is optional. The path '/Joh?n' would cover both /Jon and /John
    • +: A + says that the previous character can be repeated as many times as you like, but has to be at least once. A path of '/ni+ce' would cover /nice as well as /niiiiiiiiiiiiiiiiice
    • *: A * says that the previous character is optional and can be repeated as often as you like. A path of '/wow!*'would match /wow/wow!, or even /wow!!!!!!!!!!!!
    • (): You can also apply wildcards to a group of characters. '/(ha)+' would match /ha/haha, and /hahahahaha, but not /hah
  • Regular Expressions: If you want to go beyond basic wildcards, you can go nuts with a regular expression. With /^\/(pen-)?((pine)?apple-)+pen$/ you could match /apple-pen/pineapple-pen, or /pen-pineapple-apple-pen.
  • Parameters: Another very useful feature, is you can have parameters in your route. This lets you easily provide RESTful URLs with dynamic portions. A path of '/posts/:postId' will not only match /posts/42, but the request will contain a params.postId variable with a value of '42'.

The method and path are essential to know when to do something, but the handler is the callback function that actually gets called in those cases. A handler is passed a request, a response, and a next callback; those arguments are typically written as (req, res, next).

  • Request (req): The request contains all kinds of information about what’s been asked by the user. From here, you can access the path, parameters, headers, and a myriad of other things. For everything on a request, you can consult the API reference.
  • Response (res): The response is how you send information back to the user. The simplest way to send back data is with the .send method (e.g. res.send('Hello, world!')), but there are many other methods. Again, you can find all the methods in the API reference.
  • Next Callback (next): The next function allows you to use multiple handlers for the same route. You can use one handler to process information, and when it’s done, it can call next() to signal to move on to the next handler. If you pass in a string, it will instead throw an error, which you can catch elsewhere, or display to the user (e.g. next('You must be authenticated to access this route')).

What Is a Router in Express?

Now that you’re a little more familiar with routes, how is that different from a router? You can think of a router as a collection of routes. This can be a useful way to organize different sections of your app.

When using a router, you can think in terms of a root path, even if you’re going to be using that router from some subpath. For example, say you have an API to manage messages. You could have a router with a path '/' to GET all messages or POST a new message. You could have another path '/:id' to GET or PUT (edit) a specific message.

Your app could then take that router and host it at /messages, with app.use('/messages', messageRouter). The router itself doesn’t have to care what its global path is going to be; it can even be used in multiple routes (e.g. /messages/texts, and /email).

Create a Simple App With a Router in Node With Express

Enough talk already… let’s get to some real code. To get started, create a folder that will house all your code. Then, set up a package.json folder to help manage dependencies. You can use npm init to do this. You’ll also need to install Express.

mkdir my-first-router
cd my-first-router
npm init -y
npm install express@4.16.4 hbs@4.0.1


Create an index.js file with the following code:

index.js

const express = require('express')
const path = require('path')

const app = express()

app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'hbs')

app.get('/', (req, res) => {
  res.render('index', {
    title: 'Hello, world!',
    content: 'How are you?'
  })
})

const port = process.env.PORT || 3000
app.listen(port, () => console.log(`App listening on port ${port}`))


This tells Express to use Handlebars (hbs) as a view engine. It uses Node’s built-in path to tell it the directory containing the views. The / path is told to render the page using index.hbs, which will put the content in a paragraph (p) tag.

To make sure Express has templates to render, create a new folder called views. Then, create a new file in views called layout.hbs. When you tell Express to render a view, it will first render layout.hbs and put the content of the view inside the {{{body}}} tag. This lets you set up a skeleton for the app. Here’s some basic HTML using Bootstrap that will give you some nice styling without needing to write any CSS. This will also render the title passed into the context in your / route.

views/layout.hbs

<!doctype html>
<html lang="en">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">

    <!-- Bootstrap CSS -->
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">

    <title>{{title}}</title>
  </head>
  <body>
    <h1>{{title}}</h1>
    <main>
      {{{body}}}
    </main>
  </body>
</html>


You’ll also need to create a index.hbs view that will just be really basic for now:

views/index.hbs

<p>{{content}}</p>


To make development a little easier, you can install nodemon with:

npm install --save-dev nodemon@1.18.4


Then modify your package.json file so that the "scripts" entry includes a start script with nodemo. This will make it so that you can simply run npm start and your server will automatically restart whenever you make changes:

"scripts": {
  "start": "nodemon ."
}


Now, in your terminal, if you type npm start, you’ll start the server. You can then go to http://localhost:3000 to see the app running.

File Server Demo

Create a Router in Express

Well, that’s kind of boring. How about making it do something useful? Let’s create a simple to-do list. Start by creating a router to manage a list of items. Make a new file called todo.js:

todo.js

const express = require('express')

const router = express.Router()

let todo = []

router.post('/', (req, res, next) => {
  todo = [...req.body.todo || []]
  if (req.body.remove) todo.splice(req.body.remove, 1)
  if (req.body.new) todo.push({})

  next()
})

router.use('/', (req, res) => {
  res.render('todo', { title: 'To-do list', todo })
})

module.exports = router


Here you have two route handlers. The first one listens for POST requests (signified by router.post). It will replace the to-do list with a copy of whatever it receives from the form. If the form contains the remove property (containing an index), it will use splice to remove the element at that index. If the form contains the new property, a new item will be pushed on to the array. After it’s done modifying the to-do list, it calls next() to move on to the next route handler.

The second route handler is always used (signified by router.use). Its sole purpose is to render the to-do list. By separating the routes like this, you can easily do one thing always, and another thing only in certain circumstances (in this case on a POST request).

To tell the app to use this router, you’ll have to add a few lines to index.js:

index.js

@@ -1,11 +1,15 @@
 const express = require('express')
 const path = require('path')
+const todoRouter = require('./todo')

 const app = express()

 app.set('views', path.join(__dirname, 'views'))
 app.set('view engine', 'hbs')

+app.use(express.urlencoded({ extended: true }))
+app.use('/todo', todoRouter)
+
 app.get('/', (req, res) => {
   res.render('index', {
     title: 'Hello, world!',


Now for the todo template. It’s a little larger, so I saved it for last. If you’re familiar with HTML, it shouldn’t be too bad to follow. Handlebars add a few features that let you access variables. In this case, you’re using an {{#if}} block to render something special if there aren’t any items, as well as an {{#each}} block to render each of the list items with minimal markup.

The only JavaScript used here is for automatically submitting the form when you change something. If JavaScript were disabled, this would still work when pressing the “Enter” key on your keyboard, thanks to the hidden button labeled “Autosave”.

views/todo.hbs

<form method="post">
  <div class="row">
    <div class="col">
      <button hidden>Autosave</button>
      <button class="btn btn-success" name="new" value="true">New</button>
    </div>
  </div>
  <div class="row mt-3">
    <div class="col">
      {{#if todo.length}}
        <ul class="list-group">
          {{#each todo}}
            <li class="list-group-item d-flex align-items-center">
              <input
                type="checkbox"
                onchange="this.form.submit()"
                name="todo[{{@index}}][checked]"
                {{#if this.checked}}checked{{/if}}
              />
              <input
                name="todo[{{@index}}][text]"
                onchange="this.form.submit()"
                class="form-control mx-2"
                value="{{this.text}}"
              />
              <button class="btn btn-danger" name="remove" value="{{@index}}">Remove</button>
            </li>
          {{/each}}
        </ul>
      {{else}}
        <h5>Your To-Do List is empty</h5>
      {{/if}}
    </div>
  </div>
  <style>
    input[type=checkbox]:checked + input {
      text-decoration: line-through;
      opacity: 0.75;
    }
  </style>
</form>


Now go to http://localhost:3000/todo and enter some items into your todo list.

To-do list

Add User Authentication in Node

Now, you have a functional to-do list. You may have noticed though, this would only work if you want everyone using it to share the same list. If you add in authentication, you can have a separate to-do list for each user.

Adding users doesn’t have to be a pain. In fact, it can be done really simply with Okta, a cloud service that allows developers to create, edit, securely store user accounts, and connect them with one or multiple applications.

If you don’t already have one, sign up for a forever-free developer account.

You’re going to need to save some information to use in the app. Create a new file named .env. In it, enter your organization URL.

HOST_URL=http://localhost:3000
OKTA_ORG_URL=https://{yourOktaOrgUrl}


You will also need a random string to use as an App Secret for sessions. You can generate this with the following commands:

echo -e "\nAPP_SECRET=`npx -q uuid`" >> .env


Next, log in to your developer console, navigate to Applications, then click Add Application. Select Web, and then click Next. Give your application a name, like “My First Router”. Change the Base URI to http://localhost:3000/ and the Login redirect URI to http://localhost:3000/authorization-code/callback, then click Done

Click Edit and add a Logout redirect URI of http://localhost:3000/, then click Save.

Okta application settings

The page you come to after creating an application has some more information you need to save to your .env file. Copy in the client ID and client secret.

OKTA_CLIENT_ID={yourClientId}
OKTA_CLIENT_SECRET={yourClientSecret}


Now, back to the code. You’ll need to add Okta’s OIDC middleware to control authentication. It also relies on using sessions. You’ll need to use dotenv to read in variables from the .env file. To install the dependencies you’ll need, run this command:

npm install @okta/oidc-middleware@1.0.1 dotenv@6.1.0 express-session@1.15.6


Now, modify your index.js file. Here, you’ll be adding the session and OIDC middlewares, and a logout route so users can log out of the app. You’re also adding a middleware specifically to the todoRouter (app.use('/todo', oidc.ensureAuthenticated(), todoRouter)). By adding oidc.ensureAuthenticated(), you’re letting Okta make sure that route can’t be reached unless a user is logged in. If the user isn’t logged in and tries to reach that route, they’ll be taken to a secure site to log in, and redirected back to your site afterward.

index.js

@@ -1,14 +1,46 @@
+require('dotenv').config()
+
 const express = require('express')
 const path = require('path')
+const session = require('express-session')
+const { ExpressOIDC } = require('@okta/oidc-middleware')
+
 const todoRouter = require('./todo')

+const oidc = new ExpressOIDC({
+  issuer: `${process.env.OKTA_ORG_URL}/oauth2/default`,
+  client_id: process.env.OKTA_CLIENT_ID,
+  client_secret: process.env.OKTA_CLIENT_SECRET,
+  redirect_uri: `${process.env.HOST_URL}/authorization-code/callback`,
+  scope: 'openid profile'
+})
+
 const app = express()

+app.use(session({
+  secret: process.env.APP_SECRET,
+  resave: true,
+  saveUninitialized: false
+}))
+app.use(oidc.router)
+
 app.set('views', path.join(__dirname, 'views'))
 app.set('view engine', 'hbs')

 app.use(express.urlencoded({ extended: true }))
-app.use('/todo', todoRouter)
+app.use('/todo', oidc.ensureAuthenticated(), todoRouter)
+
+app.get('/logout', (req, res) => {
+  if (req.userContext) {
+    const idToken = req.userContext.tokens.id_token
+    const to = encodeURI(process.env.HOST_URL)
+    const params = `id_token_hint=${idToken}&post_logout_redirect_uri=${to}`
+    req.logout()
+    res.redirect(`${process.env.OKTA_ORG_URL}/oauth2/default/v1/logout?${params}`)
+  } else {
+    res.redirect('/')
+  }
+})

 app.get('/', (req, res) => {
   res.render('index', {


To make things a little easier, when a user logs out, add a link to the to-do list from the home page.

views/index.hbs

<p>{{content}}</p>
<a href="/todo">Go to To-Do List</a>


You can also add a welcome message and a log out button to your layout.hbs.

views/layout.hbs

@@ -12,6 +12,12 @@
   </head>
   <body class="container">
     <h1>{{title}}</h1>
+    {{#if userinfo}}
+      <h4>
+        Welcome back, {{userinfo.given_name}}!
+        <small><a href="/logout">Click here to log out</a></small>
+      </h4>
+    {{/if}}
     <main>
       {{{body}}}
     </main>


For that to work, you’ll need to add userinfo to the context when rendering views.

todo.js

--- a/todo.js
+++ b/todo.js
@@ -13,7 +13,7 @@ router.post('/', (req, res, next) => {
 })

 router.use('/', (req, res) => {
-  res.render('todo', { title: 'To-do list', todo })
+  res.render('todo', { title: 'To-do list', todo, userinfo: req.userContext.userinfo })
 })

 module.exports = router


index.js

@@ -43,7 +43,10 @@ app.get('/logout', (req, res) => {
 })

 app.get('/', (req, res) => {
+  const { userinfo } = req.userContext || {}
+
   res.render('index', {
+    userinfo,
     title: 'Hello, world!',
     content: 'How are you?'
   })


So, now, you’re requiring users to log in before they can edit the to-do list, but it’s still a single, shared list. In order to split it up into a separate list for each user, make another small change to todo.js.

todo.js

@@ -2,17 +2,21 @@ const express = require('express')

 const router = express.Router()

-let todo = []
+const todosByUser = {}

 router.post('/', (req, res, next) => {
-  todo = [...req.body.todo || []]
+  const todo = [...req.body.todo || []]
   if (req.body.remove) todo.splice(req.body.remove, 1)
   if (req.body.new) todo.push({})

+  todosByUser[req.userContext.userinfo.sub] = todo
+
   next()
 })

 router.use('/', (req, res) => {
+  const todo = todosByUser[req.userContext.userinfo.sub] || []
+
   res.render('todo', { title: 'To-do list', todo, userinfo: req.userContext.userinfo })
 })


Multi-User Demo

Learn more about Node, Express, and Secure Web Development

Now that you have a fully functional to-do list, I encourage you to expand on it. Try storing the data in a database, or even let Okta store it for you! See if you can create some more routers to add to the web server.

If you want to see the final code sample, you can find it on GitHub.

If you’d like to learn more about Node and Express check out some of these other articles on the Okta developer blog:

If you have any questions about this post, please add a comment below. For more awesome content, follow @oktadev on Twitter, like us on Facebook, or subscribe to our YouTube channel.

Topics:
node ,express ,routes ,tutorial ,okta ,javascript ,html ,authentication ,web dev

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}