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:
- creating endpoints to login (returning bearer token) and logout
- 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:
-
First, make sure you’re using Meteor 3, which includes Express under the hood.
-
In your server/main.js
file, import the WebApp module:
import { WebApp } from "meteor/webapp";
- 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)
}