Skip to content

Next.js - Integrate your backend

What are API Routes?

API routes provide a solution to build your API with Next.js.

Any file inside the folder pages/api is mapped to /api/* and will be treated as an API endpoint instead of a page. They are server-side only bundles and won't increase your client-side bundle size.

What's great here is that these APIs can be completely managed by services like Vercel. PropelAuth's middleware allows you to check the identity of users at the edge, meaning you can quickly determine if a user is logged in or not without needing to make a round trip to a database somewhere.

Next.js API routes support express compatible middleware, so we will use the @propelauth/express library.

How it works

You've seen that the frontend gets an access token. When it makes an HTTP request, it provides this access token in an Authorization header.

PropelAuth provides you with metadata that you use to validate the access token and figure out who it belongs to. The complexity of fetching the metadata and validating the tokens is hidden in the express library.

Installation

Next.js API routes support express compatible middleware, so we will use the @propelauth/express library.

$ npm install --save @propelauth/express
$ yarn add @propelauth/express

Initialize

In lib/propelauth.js:

import {initAuth} from "@propelauth/express";

const propelauth = initAuth({
    debugMode: true, // (1)
    authUrl: "REPLACE_ME", // (2)
    apiKey: "REPLACE_ME", // (3)
    manualTokenVerificationMetadata: { 
        verifierKey: "REPLACE_ME", // (4)
        issuer: "REPLACE_ME", // (5) 
    }
})
export default propelauth
  1. If true, error messages returned to the user will be detailed. It's useful for debugging, but a good idea to turn off in production.
  2. The base URL where your authentication pages are hosted. You can find this under the Backend Integration section for your project at https://app.propelauth.com.
  3. You can manage your api keys under the Backend Integration section for your project.
  4. You can find this under the Backend Integration section for your project at https://app.propelauth.com.
  5. You can find this under the Backend Integration section for your project at https://app.propelauth.com.

You can then import what you need from propelauth in your routes:

import {requireUser, requireOrgMember} from "../../lib/propelauth"

Running middleware in a NextJS API route

The full details are available in the official docs.

However, the main thing you need to know is you can create this helper function:

// Helper method to wait for a middleware to execute before continuing
// And to throw an error when an error happens in a middleware
export function runMiddleware(req, res, fn) {
    return new Promise((resolve, reject) => {
        fn(req, res, (result) => {
            if (result instanceof Error) {
                return reject(result)
            }

            return resolve(result)
        })
    })
}

And then use that to call middleware within your routes:

async function handler(req, res) {
    // Run the middleware
    await runMiddleware(req, res, YOUR_MIDDLEWARE)

    // Rest of the API logic
    res.json({message: 'Hello Everyone!'})
}

Protect API routes

requireUser

An Express middleware that will verify the request was made by a valid user. Specifically, it will:

  1. Check that a valid access token was provided. If not, the request is rejected with a 401 status code.
  2. Set user on the Request with the user's information.
import {requireUser} from "../../lib/propelauth"

async function handler(req, res) {
    // Verifies that a request was made with a valid access token
    await runMiddleware(req, res, requireUser)

    // req.user is now set from our requireUser middleware
    res.status(200).json({message: "Hello user with ID " + req.user.userId});
}

The user object looks like:

{
  // the id of the user whose token was provided
  userId: "2667a646-cdef-49da-8580-47e0d73cffa7",
  // For each organization the user is a member of, this is a dict of the organization's id
  //   to some information about the organization.
  orgIdToOrgMemberInfo: {
    "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4": {
      "orgId": "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4",
      "orgName": "ExampleOrganization",
      // This is the role of the user within that organization. See UserRole for more information
      "userRole": UserRole.Owner,
    }
  }
}

optionalUser

Similar to requireUser, except if an access token is missing or invalid, the request is allowed to continue, but user on the Request will be undefined.

import {optionalUser} from "../../lib/propelauth"

async function handler(req, res) {
    await runMiddleware(req, res, optionalUser)

    if (req.user) {
        res.status(200).json({message: "Hello user with ID " + req.user.userId});
    } else {
        res.status(200).json({message: "Hello anonymous"})
    }
}

requireOrgMember

An Express middleware that will verify the request was made by a valid user that belongs to a specific organization.

import {requireOrgMember} from "../../lib/propelauth"

const requireAdminInOrg = requireOrgMember({
    // Tell our middleware that the orgId comes from `req.query`
    orgIdExtractor: (req) => req.query.orgId,

    // Users who call this API must be at least an Admin within this org
    minimumRequiredRole: propelauth.UserRole.Admin,
})

async function handler(req, res) {
    await runMiddleware(req, res, requireAdminInOrg)

    // requireAdminInOrg automatically sets req.org
    res.status(200).json(
        {message: "You are an owner or admin of " + req.org.orgName}
    );
}

Specifically, it will:

  1. Check that a valid access token was provided. If not, the request is rejected with a 401 status code.
  2. Set user on the Request with the user's information. See requireUser for what the user object looks like.
  3. Find an id for an organization within the request. By default, it will check Request.params.orgId (a path parameter named orgId).
  4. Check that the user is a member of that organization. If not, the request is rejected with a 403 status code.
  5. (Optionally) Check that the user's role in that organization is >= minimumRequiredRole. If not, the request is rejected with a 403 status code.
  6. Set org on the Request with the organization's information for this user.

The org object looks like:

{
  "orgId": "2ef0e1fc-234f-4dc0-a50c-35adb1bbb7e4",
  "orgName": "ExampleOrganization",
  "userRole": UserRole.Owner, // (1)
}
  1. This is the role of the user within that organization. See UserRole for more information

The full signature for requireOrgMember is:

interface RequireOrgMemberArgs {
    minimumRequiredRole?: UserRole // (2)
    orgIdExtractor?: (req: Request) => string // (1)
}

function requireOrgMember(args?: RequireOrgMemberArgs) {
}
  1. A function that takes in the Request and outputs the orgId to require the user to be a member of. If none is specified, it will check for a path param orgId via req.params.orgId.
  2. If specified, requireOrgMember will check both that the user is a member of the organization, and that their role is >= minimumRequiredRole. If not, the request is rejected with a 403 Forbidden error.

API calls

In addition to protecting API routes, you can make requests to PropelAuth to fetch more information about your users or organizations. You can also create new users, update user metadata, etc.

// Example
const usersResponse = await fetchUsersByQuery({emailOrUsername: "@example.com", orderBy: "LAST_ACTIVE_AT_DESC"})
for (const user of userResponse.users) {
    console.log("Found user " + user)
}

See the reference for everything you can do.

Next Steps

Done with your backend? Next you can deploy to production.