Introduction
Meteor is well known for it's full-scale solution to rapidly create JavaScript applications with many flavors (SPA,PWA, Mobile native, Desktop website and more).
If you are totally new to Meteor or you know it"from the old days" then this article may give you a fresh update on what Meteor is nowadays and what it's not:

Why choose Meteor (or not) for your next project?
Jan Küster 🔥 ・ Jan 18 '21
Meteor comes with a very detailed and sophisticateddeveloper guide. It guides you through all architectural aspects and provides best-practice suggestions for architecture and design decisions.
However, it does not teach you about how to create Microservices with Meteor. This is because Meteor as a framework is very flexible and to cover every potential architectural decision would go beyond the scope of the guide.
This is why this post is here to guide you through the most important aspects of Microservices with Meteor.
Covered topics
In order to get everyone on board, we will go through the most important aspects to get a running, usable Microservice example:
- Why Microservices with Meteor
- How to create a "headless"Nanoservice with Meteor
- How to create a fullstack Microservice with Meteor
- Connect apps and service with each other (HTTP / DDP)
- Security considerations
- Deployment
All the code is also put in a repository, which I link at the end of the article.
What's not covered
The field of Microservices is very broad. Thus, I want tokeep this article focused and only scratch the surface of architectural constrains or things playing a role when communicating between services.
If you are new to Microservices and are interested in learning about them, you may start with some good standard literature:
On language and symbols
I am often switching betweenI/me,you orwe/use and by using those words I am referring to different contexts:
- I/me - Reflecting my choices, intentions or experience
- you/yours - Provoking you to think about a certain aspect of the article or topic
- we/us - Hands-on situation or practical task, where you should think of us as a small team that currently works together
- 🤓 - These paragraphs add background details for those who exactly want to know what's going on and why. If it's too much information you can skip them for now and read them later.
The context
To make this much more accessible we should think in a concrete use-case for such a service. Let's say we want to implement an online-shop that has some kind of connection to a warehouse.
At the same time there should be a catalog of products, where a person (catalog-manager) can insert and update new product entries.
Finally, the availability of a product should be updated, based on the physical availability in the warehouse.
Derived from this we can split our application and services into the following:
- shop (application)
- catalog-service (Microservice)
- warehouse-status-service (Nanoservice)
The architecture could look like this:
Why Microservices with Meteor
This should always be the very first question: why using a certain tech or stack to solve a specific problem. If you can't answer this question then you may rethink your decision. Here are some examples on why I chose Meteor:
Well-established stack
Meteor offers a full stack out of the box. It brings bundling, package management (NPM/Meteor packages), transport (server/client) and zero config required. Additionally it fully supports TypeScript, as well as the most popular frontends like React, Angular, Vue and Svelte (plus it's own client engine "Blaze").
If we can control the full stack with nearly no integration effort we can easily create a new Microservice in a few steps.
One language to rule them all
Furthermore, since Meteor uses one language (JavaScript) for this whole stack we can easily onboard newcomers into a project and assign them one service. This maximizes the focus, because there is one language, one framework and one Microservice to cover.
DB-Integration
As already mentioned Meteor comes with a tight integration for MongoDB. While this is often criticized for being less flexible, it actually allows us to easily implement "data ownership", where services have their own database: even if we have one MongoDB provider we can assign every service a database, simply by putting theMONGO_URL
to the application environment variables with the respective database name. This allows us to keep services separated not only in terms of code but also in terms of data.
Time-to-market
The step from development to deployment is very fast, since there is no bundler, minifier, babel and whatnot to be configured. It's all there already, so you just need to deploy in one step toGalaxy (Meteor-optimized hosting from the official Developers) or useMeteor-Up to deploy to any other service provider you can imagine.
This all leads to a very short time-to-market and allows you to rapidly add new Microservices to your infrastructure or update them without fiddling into complex configurations.
For the next steps we will get our hands-on Meteor and create our own Microservice example in about 15 Minutes.
Install Meteor
If you haven't installed Meteor on your machine, just follow the steps from theofficial install website:
curl https://install.meteor.com/ | sh
or on Windows:
npm install -g meteor
There is also another post that can help you decide, which frontend framework you might use for your next Meteor apps:
How to create a "headless" Nanoservice with Meteor
Step 1 - Create the most minimal Meteor app
For our warehouse we will create the most minimal Meteor app possible. In order to do so, let's create abare project:
$meteor create--bare warehouse$cdwarehouse$meteor npminstall
Thiswarehouse
service now contains no code and only a very minimal list of Meteor packages (see.meteor/packages
in thewarehouse
project):
meteor-base@1.5.1 # Packages every Meteor app needs to havemobile-experience@1.1.0 # Packages for a great mobile UXmongo@1.12.0 # The database Meteor supports right nowstatic-html # Define static page content in .html filesreactive-var@1.0.11 # Reactive variable for trackertracker@1.2.0 # Meteor's client-side reactive programming librarystandard-minifier-css@1.7.3 # CSS minifier run for production modestandard-minifier-js@2.6.1 # JS minifier run for production modees5-shim@4.8.0 # ECMAScript 5 compatibility for older browsersecmascript@0.15.2 # Enable ECMAScript2015+ syntax in app codetypescript@4.3.2 # Enable TypeScript syntax in .ts and .tsx modulesshell-server@0.5.0 # Server-side component of the `meteor shell` command
Well, we can squeeze this even further! This service is "headless" (contains no client-side code), thus we can remove a few unnecessary packages here:
$meteor remove mobile-experience static-html reactive-var tracker standard-minifier-css es5-shim shell-server
Now this is the smallest possible set of packages for our headless nano-service:
meteor-base@1.5.1# Packages every Meteor app needs to havemongo@1.12.0# The database Meteor supports right nowstandard-minifier-js@2.6.1# JS minifier run for production modeecmascript@0.15.2# Enable ECMAScript2015+ syntax in app codetypescript@4.3.2# Enable TypeScript syntax in .ts and .tsx modules
Since our warehouse service will make some HTTP requests to the catalog service (to update some product availability), we add one more package here:
$meteor add http
🤓 Whyhttp
and notfetch
Note: we could instead use thefetch
package, which is basically a wrapper fornode-fetch
but I love the ease of use ofhttp
, which is why I chose it here.
Step 2 - Implement the warehouse service
First, we create a new main server file:
$mkdir-p server$touch ./server/main.js
Then we add the following code:
import{Meteor}from'meteor/meteor'import{HTTP}from'meteor/http'// fake data for some productsconstproductIds=['012345','abcdef','foobar']constrandomProductId=()=>productIds[Math.floor(Math.random()*3)]constrandomAvailable=()=>Math.random()<=0.5Meteor.startup(()=>{Meteor.setInterval(()=>{constparams={productId:randomProductId(),available:randomAvailable()}constresponse=HTTP.post('http://localhost:3000/warehouse/update',{params})if(response.ok){console.debug(response.statusCode,'updated product',params)}else{console.error(response.statusCode,'update product failed',params)}},5000)// change this value to get faster or slower updates})
What's happening here?
When the application start has completed (Meteor.startup
) we want to safely execute an interval (Meteor.setInterval
), where we call our remote endpointhttp://localhost:3000/warehouse/update
with someproductId
andavailable
parameters.
That's it.
🤓 More background
The product id's are random from a fixed set of hypothetical ids - we assume these ids exist. In a real service setup you might either want to synchronize the data between warehouse and catalog or - as in this example - use an implicit connection, based on theproductId
, which requires the product manager to enter when updating the catalog.
With the first example you ensure a high data integrity, while you also introduce a soft step towards coupling the services. The second option is free of any coupling but it requires the catalog to contain the products before the warehouse can update them.
Step 3 - Run the service
Finally, let's run thewarehouse
on port 4000:
$meteor--port=4000
We can ignore the error messages for now, since our catalog service is not established yet. It will be the subject of focus in the next section.
How to create a fullstack Microservice with Meteor
Step 1 - Create a normal Meteor app
A normal app? Yes, a Microservice can be an app that covers the full stack! The scope is not architectural but domain driven.
Therefore let's go back to our project root and create a new Meteor app:
$cd ..# you should be outside of warehouse now$meteor create--blaze catalog-service$cdcatalog-service$meteor npminstall--save bcrypt body-parser jquery mini.css simpl-schema$meteor add reactive-dict accounts-password accounts-ui aldeed:autoform communitypackages:autoform-plain leaonline:webapp jquery@3.0.0!
🤓 What are these packages for?
name | description |
---|---|
brypt | Used with accounts for hashing passwords |
body-parser | Used to proper decode json from post request body that are not usingapplication/x-www-form-urlencoded |
jquery | Makes life easier on the client |
mini.css | Minimal css theme, optional |
simpl-schema | Used byaldeed:autoform to create forms from schema and validate form input |
reactive-dict | Reactive dictionary for reactive states |
accounts-password | Zero config accounts system with passwords |
accounts-ui | Mock a register/login component for fast and easy creation of accounts |
aldeed:autoform | Out-of-the-box forms from schemas |
communitypackages:autoform-plain | Plain, unstyled forms theme |
leaonline:webapp | Drop-in to enablebody-parser withwebapp |
jquery@3.0.0! | Force packages to use latest npm jquery |
Step 2 - Create the backend
For our backend we mostly need a new Mongo Collection that stores our products and some endpoints to retrieve them (for the shop) and update their status (for the warehouse).
Step 2.1 - Create products
First we create a new Products collection that we will use in isomoprhic fashion on server and client:
$mkdir-p imports$touch ./imports/Products.js
TheProducts.js
file contains the following
import{Mongo}from'meteor/mongo'exportconstProducts=newMongo.Collection('products')// used to automagically generate forms via AutoForm and SimpleSchema// use with aldeed:collection2 to validate document inserts and updatesProducts.schema={productId:String,name:String,description:String,category:String,price:Number,available:Boolean}
If you are too lazy to enter the products by yourself (as I am) you can extend this file by the following code to add some defaults:
constfixedDocs=[{productId:'foobar',name:'Dev Keyboard',description:'makes you pro dev',category:'electronics',price:1000,available:true},{productId:'012345',name:'Pro Gamepad',description:'makes you pro gamer',category:'electronics',price:300,available:true},{productId:'abcdef',name:'Pro Headset',description:'makes you pro musician',category:'electronics',price:800,available:true}]// to make the start easier for you, we add some default docs hereMeteor.startup(()=>{if(Products.find().count()===0){fixedDocs.forEach(doc=>Products.insert(doc))}})
Step 2.2 - Create HTTP endpoint for warehouse
Now we import Products in ourserver/main.js
file and provide the HTTP POST endpoint that will later be called by thewarehouse
nanoservice. Therefore, we remove the boilerplate code fromserver/main.js
and add our endpoint implementation here:
import{Meteor}from'meteor/meteor'import{WebApp}from'meteor/webapp'importbodyParserfrom'body-parser'import{Products}from'../imports/Products'consthttp=WebApp.connectHandlers// proper post body encodinghttp.urlEncoded(bodyParser)http.json(bodyParser)// connect to your logger, if desiredconstlog=(...args)=>console.log(...args)// this is an open HTTP POST route, where the// warehouse service can update product availabilityhttp.use('/warehouse/update',function(req,res,next){const{productId,available}=req.bodylog('/warehouse/update',{productId,available})if(Products.find({productId}).count()>0){consttransform={productId:productId,available:available==='true'// http requests wrap Boolean to String :(}// if not updated we respond with an error code to the serviceconstupdated=Products.update({productId},{$set:transform})if(!updated){log('/warehouse/update not updated')res.writeHead(500)res.end()return}}res.writeHead(200)res.end()})
🤓 More background
For those of you who look for anexpress
route - Meteor comes already bundled withconnect
, which is a more low-level middleware stack. It's express compatible but works perfect on it's own.
Furthermore, our endpoint skips any updates on products that are not found. In reality we might return some 404 response but this will be up to your service design.
Note, that even withbody-parser
we still need to parse the Boolean values, that have been parsed to strings during the request ("true"
and"false"
instead oftrue
andfalse
).x
Step 2.3 - Create DDP endpoints for the shop
In order to provide some more powerful service with less coding effort we actually also want to have some Data available the Meteor way.
Our shop will then be able to subscript to data and "automagically" resolve the response into a client-side Mongo Collection.
Extend yourserver/main.js
file by the following code:
// We can provide a publication, so the shop can subscribe to productsMeteor.publish('getAvailableProducts',function({category}={}){log('[publications.getAvailableProducts]:',{category})constquery={available:true}if(category){query.category=category}returnProducts.find(query)})// We can provide a Method, so the shop can fetch productsMeteor.methods({getAvailableProducts:function({category}={}){log('[methods.getAvailableProducts]:',{category})constquery={available:true}if(category){query.category=category}returnProducts.find(query).fetch()// don't forget .fetch() in methods!}})
That's all for our backend right now. We will not implement any authentication mechanisms as this will totally blow the scope of this article.
In the next step we will create a minimal frontend for the catalog manager, including a login and a form to insert new products.
Step 3 - Create the frontend
Step 3.1 - Add HTML Templates
The frontend code is located in theclient
folder. First, let's remove the boierplate code fromclient/main.html
and replace it with our own:
<head><title>catalog-service</title></head><body><h1>Catalog service</h1>{{#unless currentUser}} {{> loginButtons}}{{else}} {{> products}}{{/unless}}</body><templatename="products"><ul> {{#each product in allProducts}}<li><div> {{product.productId}} - {{product.name}} {{#if product.available}})(available){{else}}(not available){{/if}}</div><div>{{product.description}}</div></li> {{else}}<li>No products yet!</li> {{/each}}</ul><buttonclass="addProduct">Add product</button> {{#if addProduct}} {{> quickForm schema=productSchema type="normal"}} {{/if}}</template>
🤓 What's going on here?
This template renders all our products in a list (ul
) and also displays their current status. If the user is logged in. Otherwise it renders the login screen. If the user clicks on the "Add product" button, she can acutally enter new products using thequickForm
generated from theProduct.schema
that is passed by theproductSchema
Template helper.
Step 3.2 - Add Template logic
The above Template code relies on some helpers and events, which we implement inclient/main.js
:
/* global AutoForm */import{Template}from'meteor/templating'import{Tracker}from'meteor/tracker'import{ReactiveDict}from'meteor/reactive-dict'import{Products}from'../imports/Products'importSimpleSchemafrom'simpl-schema'import{AutoFormPlainTheme}from'meteor/communitypackages:autoform-plain/static'import'meteor/aldeed:autoform/static'import'mini.css/dist/mini-dark.css'import'./main.html'// init schema, forms and themingAutoFormPlainTheme.load()AutoForm.setDefaultTemplate('plain')SimpleSchema.extendOptions(['autoform'])// schema for inserting products,// Tracker option for reactive validation messagesconstproductSchema=newSimpleSchema(Products.schema,{tracker:Tracker})Template.products.onCreated(function(){constinstance=thisinstance.state=newReactiveDict()})Template.products.helpers({allProducts(){returnProducts.find()},productSchema(){returnproductSchema},addProduct(){returnTemplate.instance().state.get('addProduct')}})Template.products.events({'click .addProduct'(event,templateInstance){event.preventDefault()templateInstance.state.set('addProduct',true)},'submit #addProductForm'(event,templateInstance){event.preventDefault()constproductDoc=AutoForm.getFormValues('addProductForm').insertDocProducts.insert(productDoc)templateInstance.state.set('addProduct',false)}})
🤓 What's going on here?
At first we initialize theAutoForm
that will render an HTML form, based onProducts.schema
.
Then we create a new state variable in theTemplate.products.onCreated
callback. This state only tracks, whether the form is active or not.
TheTemplate.products.helpers
are reactive, since they are connected to reactive data sources (Products.find
andTemplate.instance().state.get
).
TheTemplate.products.events
simply handle our buttons clicks to switch the state or insert a new Product into the collection.
Step 4 - Run the service
Now with these few steps we created a full-working Microservice. Let's run it onlocalhost:3000
(we agreed in warehouse to use this port, useMeteor.settings
to easily configure those dynamically).
$meteor
Then open your browser onlocalhost:3000
and register a new user / log in with the user and with the warehouse service update the availability status of our products. 🎉
Create the shop app
Now the last part of our hands-on is to create a minimal shop that uses Meteor's DDP connection to subscribe to all available productsLIVE!
The shop itself doesn't contain any backend code so it won't take much time to get it running:
$cd ..# you should be outside catalog-service now$meteor create--blaze shop$cdshop$meteor npminstall--save jquery mini.css
Then, as with catalog-service, replace theclient/main.html
with our own template code:
<head><title>shop</title></head><body><h1>Welcome to our Shop!</h1>{{> products}}</body><templatename="products"><h2>Subscribed products (live)</h2><ul> {{#each product in subscribedProducts}}<li>{{product.name}}</li> {{else}}<li>Currently no products available</li> {{/each}}</ul><h2>Fetched products (not live)</h2><ul> {{#each product in fetchedProducts}}<li>{{product.name}}</li> {{else}}<li>Currently no products available</li> {{/each}}</ul></template>```Do the same with `client/main.js`:```jsimport { Template } from 'meteor/templating'import { Mongo } from 'meteor/mongo'import { ReactiveVar } from 'meteor/reactive-var'import { DDP } from 'meteor/ddp-client'import 'mini.css/dist/mini-dark.css'import './main.html'// at very first we establish a connection to our catalog-service// in a real app we would read the remote url from Meteor.settings// see: https://docs.meteor.com/api/core.html#Meteor-settingsconst remote = 'http://localhost:3000'const serviceConnection = DDP.connect(remote)// we need to pass the connection as option to the Mongo.Collection// constructor; otherwise the subscription mechanism doesn't "know"// where the subscribed documents will be storedexport const Products = new Mongo.Collection('products', { connection: serviceConnection})Template.products.onCreated(function () { // we create some reactive variable to store our fetch result const instance = this instance.fetchedProducts = new ReactiveVar() // we can't get our data immediately, since we don't know the connection // status yet, so we wrap it into a function to be called on "connected" const getData = () => { const params = { category: 'electronics' } // option 1 - fetch using method call via remote connection serviceConnection.call('getAvailableProducts', params, (err, products) => { if (err) return console.error(err) // insert the fetched products into our reactive data store instance.fetchedProducts.set(products) }) // options 2 - subscribe via remote connection, documents will be // added / updated / removed to our Products collection automagically serviceConnection.subscribe('getAvailableProducts', params, { onStop: error => console.error(error), onReady: () => console.debug('getAvailableProducts sub ready') }) } // we reactively wait for the connected status and then stop the Tracker instance.autorun(computation => { const status = serviceConnection.status() console.debug(remote, { status: status.status }) if (status.connected) { setTimeout(() => getData(), 500) computation.stop() } })})Template.products.helpers({ subscribedProducts () { return Products.find({ available: true }) }, fetchedProducts () { return Template.instance().fetchedProducts.get() }})```Now run the app on a different port than 3000 or 4000 and see the avialable products getting magically appear and the non-available ones disappear:```bash$ meteor --port=5000```We have finished our example project :tada:**:nerd_face: What's going on here?**The shop uses a DDP-connection to the running `catalog-service` app and subscribes to the publication we created in [Step 2.3](#create-ddp). Since we add this connection the client Mongo Collection, Meteor knows that the received documenmts have to be placed in this collection. Since queries on the client are reactive our Template engine detects changes of these updates and re-renders, based on the new data.## Security considerationsWe have created some services that communicate with each other by given endpoints. However, these services do neither verify the integrity of the data nor authenticate the source of the requests. This is an advanced topic and may be covered in future articles.Also note, that the `catalog-service` contains the `autoupdate` package to automatically return any data to any client and the `insecure` package, allowing client-side inserts to be synchronized to the server collection.These packages are super nice for mocking new prototypes of projects but **you should remove them and implement authentication and verification procedures**.Many of these topics are covered in the [Meteor guide's security section](https://guide.meteor.com/security.html).## DeploymentThe deployment of these apps is a topic for itself. With more services added to the infrastructure the complexity of deployment increases, too.In general you can rely on Meteor Software's Galaxy solution, which allows you to deploy your apps and services in one step. It also deploys them on a Meteor-optimized AWS configuration and brings APM tools out-of-the-box.If you run your own infrastructure or want to use a different provider then you may check out [Meteor-up](https://meteor-up.com/), which allows you to deploy to any server in one step with a few configurations added to a JSON file.In general you should read on the [deployment guide](https://guide.meteor.com/deployment.html) which covers both solutions and many more topics, like settings files, CDN or SEO.## Summary and outlookThis article was a short introduction to Microservices with Meteor and should provide enough insights to get you something running.From here you can extend the example or create your own ones. Notice, that security measures were not part of the article and should therefore be taken seriously, before getting your services out in the wild.## Further resourcesAll the code of the hands-on is located on this repository:
jankapunkt / microservices-with-meteor
An example setup to show how to use Microservices with Meteor
Microservices with Meteor
An example setup to show how to use Microservices with Meteor.
Read the article on:https://dev.to/jankapunkt/microservices-with-meteor-40la
More of my articles on Meteor:
Beginners
- Why choose Meteor (or not) for your next project?c
- Transform any Meteor App into a PWA
- Bootstrapping an Admin account in Meteor
Advanced
- Meteor and standard lint
- Plugin architecture with Meteor
- Meteor browser bundle and Node-Stubs - beware what you import
<hr>
I regularly publish articles here on dev.to aboutMeteor andJavaScript. If you like what you are reading and want to support me, you cansend me a tip via PayPal.
You can also find (and contact) me onGitHub,Twitter andLinkedIn.
Keep up with the latest development on Meteor by visiting&utm_medium=online&utm_campaign=Q2-2022-Ambassadors">their blog and if you are the same into Meteor like I am and want to show it to the world, you should check out the&utm_medium=online&utm_campaign=Q2-2022-Ambassadors">Meteor merch store.
Top comments(1)

- LocationSan Francisco
- EducationB.S. in Neurobiology
- WorkCo-founder at Travers+Todd
- Joined
Just want to express appreciation for this great, detailed write-up. I have two Meteor apps that connect to the same db (one is for all users while the other is limited to administrators), and your article provides more ideas for how data could be shared across apps.
For further actions, you may consider blocking this person and/orreporting abuse