Meteor with Express

Next time I need to implement a RESTful API inside my Meteor app, I will implement Express.

Out of the box, Meteor does not offer a RESTful API for non-meteor clients. Meteor specific packages are available (ie: restivus), but Meteor is phasing out it's Atmosphere package system and transitioning towards the NPM. And when possible, I'd rather choose an established, broadly used framework.

Here, I will show you how to serve a RESTful API using Express, mounted in your Meteor server-side. We'll use such tools as Express middleware and Mongo collections.

Also, since Meteor implements the latest JavaScript features with zero configuration, we will use async / await in our Express route handlers.

See the complete example on github.

Start by installing Meteor if you haven't already.

Our First API Route

In your terminal, run

$ meteor create meteor-with-express-example

... computer does stuff ...

$ cd meteor-with-express-example

Next in the terminal, install Express.

$ meteor npm install --save express

We use the 'meteor' prefix with this command. If you install npm globally as most Node developers do, you could eliminate 'meteor' and use the normal 'npm install ...'. However, there have been bugs concerning the cpu and using the 'meteor' command is recommended. Read more here

Let's start coding. Open the file '/server/main.js' and add two lines of code:

import { Meteor } from 'meteor/meteor';  
import { setupApi } from './imports/api'; // import our API

Meteor.startup(() => {  
  setupApi(); // instantiate our new Express app
});

// /server/main.js

We are going to import a function, only on the server, that creates our Express app instance along with all its routes.

Create a file, '/server/imports/api/index.js' and include some brief starter code:

import { Meteor } from 'meteor/meteor';  
import express from 'express';

export function setupApi() {  
  const app = express();

  app.get('/api', (req, res) => {
    res.status(200).json({ message: 'Hello World!!!'});
  });

  WebApp.connectHandlers.use(app);
}

// /server/imports/api/index.js

Two things going on. First, we import Express, just as we would in a normal Node app, and create a new app instance with a single route. Second, instead of Express's usual app.listen(...), we mount it in our Meteor app with the line WebApp.connectHandlers.use(app);

Meteor's WebApp package serves an important role for Meteor in general and now in this specific case it exposes our Express app to the world. Read more about the WebApp package here.

Let's test our new api endpoint. In another terminal, make an HTTP GET request to that endpoint. I'll use curl.

$ curl http://localhost:3000

Voila:

Example shows working route

Next, let's manage our Widgets collection.

Widgets Collection

We are going to make a CRUD app to manage our Widget collection, starting with 'index' and 'create' endpoints. We are sticking to the simplest possible desig. Our collection will feature no schemas, no hooks, no instance methods and no custom Meteor methods.

Create a new file '/imports/api/widgets.js` and start with this:

const Widgets = new Mongo.Collection('widgets');

export default Widgets;

// /imports/api/widgets.js

Note, this directory is 'API' as in, our Meteor app's interface. what it uses to conduct all it's business logic. That's different than 'RESTful API' as in, a selection of HTTP routes served by our express app. Confusing, maybe, but Meteor recommends we include our business logic in this '/imports/api' directory.

Now let's add a route to get all our Widgets. Add some lines to '/server/imports/api/index.js':

import { Meteor } from 'meteor/meteor';  
import express from 'express';

import Widgets from '../../../imports/api/widgets'; // import the Widgets collection

async function getWidgets(req, res) {  
  const widgets = await Widgets.find().fetch();

  res.status(200).json({ data: widgets });
}

export function setupApi() {  
  const app = express();

  app.get('/api/widgets', getWidgets);
  app.get('/api', (req, res) => {
    res.status(200).json({ message: 'Hello World!!!' });
  });

  WebApp.connectHandlers.use(app);
}


// /server/imports/api/index.js

We import our new Widgets collection, add a route to get widgets, and create a new async function to handle that route. It waits for results from 'Widgets.find().fetch()' and sends back all our widgets:

Finding an array of 0 widgets

We have no widgets. Let's add a new route to create a widget. In '/server/imports/api/index.js':

import { Meteor } from 'meteor/meteor';  
import express from 'express';

import Widgets from '../../../imports/api/widgets';

async function getWidgets(req, res) {  
  const widgets = await Widgets.find().fetch();

  res.status(200).json({ data: widgets });
}

async function createWidget(req, res) {  
  const widgetId = await Widgets.insert({ name: 'foo', color: 'blue' });

  res.status(201).json({ data: widgetId });
}

export function setupApi() {  
  const app = express();

  app.get('/api/widgets', getWidgets);
  app.post('/api/widgets', createWidget);
  app.get('/api', (req, res) => {
    res.status(200).json({ message: 'Hello World!!!' });
  });

  WebApp.connectHandlers.use(app);
}

// /server/imports/api/index.js

We added a single route that handles a POST to '/api/widgets' and responds with the widget's id.

I'll switch to a graphical HTTP client now, so it's easier to see:

Paw shows working POST to /api/widgets

Note, I'm using this Mac OS X app: https://paw.cloud/

Our 'create widget' route works. How about a route to get a single widget?

Add another route to the API:

...

async function getWidget(req, res) {  
  const widget = await Widgets.findOne(req.params.id);

  res.status(200).json({ data: widget });
}

...

export function setupApi() {  
  const app = express();

  app.get('/api/widgets/:id', getWidget);
  app.get('/api/widgets', getWidgets);
  app.post('/api/widgets', createWidget);
  app.get('/api', (req, res) => {
    res.status(200).json({ message: 'Hello World!!!' });
  });

  WebApp.connectHandlers.use(app);
}

// /server/imports/api/index.js

We added a new async function to get a single widget by id, and we used that function in a new route.

Let's quickly turn this into a complete CRUD app, so we can create, read and also update and destroy widgets. Also, let's refactor so 'createWidget' and 'updateWidget' both accept form data.

Express uses a special 'middleware' to parse requests and add a 'body' to the request. Middleware are functions that run in between when our app receives a request and sends a response. Each middleware function takes the request, does something to it, and passes it along to the next middleware. This process ends at a route handler which considers the request and sends out a response.

We will use the 'body-parser' middleware to accept incoming form data.

In your terminal, run:

$ meteor npm install --save body-parser

Import it in your api file and use it, as below:

...
import bodyParser from 'body-parser';

...

export function setupApi() {  
  const app = express();
  app.use(bodyParser.urlencoded({ extended: false }));
  ...
}

// /server/imports/api.index

Before we test, review the entire api file:

import { Meteor } from 'meteor/meteor';  
import express from 'express';  
import bodyParser from 'body-parser';

import Widgets from '../../../imports/api/widgets';

async function getWidget(req, res) {  
  const widget = await Widgets.findOne(req.params.id);

  res.status(200).json({ data: widget });
}

async function updateWidget(req, res) {  
  const result = await Widgets.update(req.params.id, { $set: { name: req.body.name, color: req.body.color } });
  const widget = await Widgets.findOne(req.params.id);

  res.status(200).json({ data: widget });
}

async function deleteWidget(req, res) {  
  const result = await Widgets.remove(req.params.id);

  res.status(202).json({ data: result });
}

async function getWidgets(req, res) {  
  const widgets = await Widgets.find().fetch();

  res.status(200).json({ data: widgets });
}

async function createWidget(req, res) {  
  const widgetId = await Widgets.insert({
    name: req.body.name,
    color: req.body.color
  });
  const widget = await Widgets.findOne(widgetId);

  res.status(201).json({ data: widget });
}

export function setupApi() {  
  const app = express();
  app.use(bodyParser.urlencoded({ extended: false }));

  app.get('/api/widgets/:id', getWidget);
  app.put('/api/widgets/:id', updateWidget)
  app.delete('/api/widgets/:id', deleteWidget)
  app.get('/api/widgets', getWidgets);
  app.post('/api/widgets', createWidget);
  app.get('/api', (req, res) => {
    res.status(200).json({ message: 'Hello World!!!' });
  });

  WebApp.connectHandlers.use(app);
}


// /server/imports/api/index.js

Important Note! All these route handlers should include try/catch blocks if you want to see and respond to errors. For this example, we're cruising along without any error handling.

Testing in our http client:

showing the full CRUD features of our api

We can create a widget, get a widget, update and delete a widget. CRUD!

More to Do

This is a preliminary investigation. Express seems to work well in this situation now that Meteor implemented the module 'import' feature. Still, we could experiment more.

What about authentication? Ideally, users could sign in via this API, perhaps hooking onto Meteor Accounts, and then access protected resources. Presumably we could create a PassportJS strategy that found the user in the Meteor.users collection and authenticated him by password.

Sounds like a great feature our backlog!

See the complete example on github.

Have fun!