Meteor-rest equivalent for meteor v3

In meteor v2 Ive previously used meteor-rest to create a json api for my meteor app. meteor-rest is no longer supported, and in meteor v3 I understand that I can use Express and its ecosystem. Does anyone have any good pointers (beyond the migration guide) for doing this and:

  1. creating endpoints to login (returning bearer token) and logout
  2. creating middleware to use that bearer token in my custom rest routes

or perhaps some hints to an alternative strategy?

bw

Matt

1 Like

Can you post the code you are actually using for those endpoints so people can help you with a better example of how to migrate them?

Per the Meteor v3 docs AI:

To set up a REST endpoint in Meteor 3, you can leverage the new Express integration. Here’s how you can do it:

  1. First, make sure you’re using Meteor 3, which includes Express under the hood.

  2. In your server/main.js file, import the WebApp module:

import { WebApp } from "meteor/webapp";
  1. Then, you can use WebApp.handlers as your Express instance to define your REST endpoints. Here’s an example of how to create a GET endpoint:
import { LinksCollection } from "/imports/api/links";
import { WebApp } from "meteor/webapp";

WebApp.handlers.get("/all-links", async (req, res) => {
  const links = await LinksCollection.find().fetchAsync();
  res.json(links);
});

This code sets up a GET endpoint at /all-links that returns all links from the LinksCollection as JSON Meteor v3 uses express under the hood – How to use and deploy it..

You can define other HTTP methods (POST, PUT, DELETE, etc.) in a similar manner using WebApp.handlers.

This approach allows you to easily create RESTful APIs or serve static files using the familiar Express syntax within your Meteor 3 application Embracing the Future: Meteor 3 with Node v20 and Express.

Remember that this is a new feature in Meteor 3, so make sure you’re using the latest version to take advantage of this Express integration.

This is the current code that Im using …

import { Accounts } from 'meteor/accounts-base'
import { check, Match } from 'meteor/check'
import { WebApp } from 'meteor/webapp'
import bearerToken from 'express-bearer-token'
import bodyParser from 'body-parser'

// provides middleware and rest end points to be used with the rest api
// inspired by [simple:rest](https://atmospherejs.com/simple/rest)

// express middleware for authenticating a user resume token
export const MeteorRestAuthentication = (opts) => {
  const bearerTokenMiddleware = bearerToken(opts)

  const authMiddleware = async (req, res, next) => {
    if (req.token) {
      const hashedToken = Accounts._hashLoginToken(req.token)
      const user = await Meteor.users.findOneAsync({
        'services.resume.loginTokens.hashedToken': hashedToken,
      })
      if (user) {
        req.userId = user._id
      }
    }
    next()
  }

  return [bearerTokenMiddleware, authMiddleware]
}

// code to add login and logout routes, run from server
export const addRestAuthRoutes = () => {
  const app = WebApp.express()
  const router = WebApp.express.Router()

  // login route - POST username or email address and password
  // returns bearer token if successful
  router.post('/users/login', bodyParser.urlencoded({ extended: true }), async (req, res) => {
    // note that bodyParser middleware enables req.body to yield js object of form content in following line
    const options = req.body

    let user
    if (options.email) {
      check(options, {
        email: String,
        password: String,
      })
      user = await Meteor.users.findOneAsync({ 'emails.address': options.email })
    } else {
      check(options, {
        username: String,
        password: String,
      })
      user = await Meteor.users.findOneAsync({ username: options.username })
    }

    if (!user) {
      res.status(400).send({ message: 'Bad Request' })
    } else {
      const result = await Accounts._checkPasswordAsync(user, options.password)
      check(result, {
        userId: String,
        error: Match.Optional(Meteor.Error),
      })

      if (result.error) {
        res.status(400).send({ message: 'Bad Request' })
      } else {
        const stampedLoginToken = Accounts._generateStampedLoginToken()
        check(stampedLoginToken, {
          token: String,
          when: Date,
        })

        await Accounts._insertLoginToken(result.userId, stampedLoginToken)

        var tokenExpiration = Accounts._tokenExpiration(stampedLoginToken.when)
        check(tokenExpiration, Date)

        res.send({
          id: result.userId,
          token: stampedLoginToken.token,
          tokenExpires: tokenExpiration,
        })
      }
    }
  })

  router.get('/users/logout', MeteorRestAuthentication(), async (req, res) => {
    if (req.userId) {
      // the existence of userId on the request signals an existing login token
      // (see middleware above) so no need to confirm
      const hashedToken = Accounts._hashLoginToken(req.token)
      await Meteor.users.updateAsync(
        { _id: req.userId },
        { $pull: { 'services.resume.loginTokens': { hashedToken: hashedToken } } },
      )
      res.send({ message: 'OK' })
    } else {
      res.status(400).send({ message: 'Bad Request' })
    }
  })

  app.use('/', router)
  WebApp.handlers.use(app)
}