Breaking changes
MongoDB Methods in the server
As mentioned in the Frequently Asked Questions, insert
, update
, remove
, find
, findOne
, upsert
methods no longer work in the server.
You should migrate to use their Async
counterparts.
const docs = MyCollection.find({ _id: '123' }).fetch(); This will not work in the server
const doc = MyCollection.findOne({ _id: '123' }); This will not work in the server
// in Meteor 3.x you should use the Async methods
const docs = await MyCollection.find({ _id: '123' }).fetchAsync(); This will work in the server
const doc = await MyCollection.findOneAsync({ _id: '123' }); This will work in the server
CLI
vue2
The --vue2
flag is no longer available. We droped support for vue2. You can see more information in this PR.
Why?
This was decided because vue2 reached its end of life on 2023-12-31, the team decided to drop support for it.
reset
The meteor reset
command clears only the local cache by default. Using the --db
option will also delete the local Mongo database. Ensure your CI flows accommodate any changes by passing the --db
option if needed.
Why?
This command is often recommended to fix your development project by clearing the cache. Previously, it also cleared the local MongoDB, which could accidentally delete important data.
Node v20
Meteor 3.0 is now using Node v20. This means that if you have any dependencies or usages of Node v14, you will need to update them to be compatible with Node v20.
NPM Installer Update
The npm installer for Meteor has been changed. For the official release, you can install Meteor with this command:
npx meteor
While we’re in the Release Candidate phase, use:
npx meteor
or specify a version directly:
npx meteor@<version>
Ensure you're using Node version 20.0.0 or higher, especially in your CI/CD workflows, to be compatible with the latest Meteor version.
Call x CallAsync
TIP
You can check call x callAsync page for a full overview.
Due to how meteor now works with async/await
, you should use callAsync
instead of call
in your methods.
In Meteor 2.x this was a common pattern:
import { Meteor } from 'meteor/meteor'
Meteor.methods({
async getAllData() {
return await MyCollection.find().fetch();
},
async otherMethod() {
return await MyCollection.find().fetch();
}
});
Meteor.call('getAllData')
Meteor.call('otherMethod')
Now in Meteor 3.x it should become:
import { Meteor } from 'meteor/meteor'
Meteor.methods({
async getAllData() {
return await MyCollection.find().fetchAsync();
},
async otherMethod() {
return await MyCollection.find().fetchAsync();
}
});
await Meteor.callAsync('getAllData')
await Meteor.callAsync('otherMethod')
WebApp Switches to Express
TIP
WebApp has switched to Express from Connect. This upgrade lets you use all the Express features in your Meteor app. If you've customized the WebApp package before, please verify if those customizations work with Express.
The webapp
package now exports these new properties:
type ExpressModule = {
(): express.Application;
json: typeof express.json;
raw: typeof express.raw;
Router: typeof express.Router;
static: typeof express.static;
text: typeof express.text;
urlencoded: typeof express.urlencoded;
};
export declare module WebApp {
// ...
/**
* @deprecated use handlers instead
*/
var connectHandlers: express.Application;
var handlers: express.Application;
/**
* @deprecated use rawHandlers instead
*/
var rawConnectHandlers: express.Application;
var rawHandlers: express.Application;
var httpServer: http.Server;
var expressApp: express.Application;
var express: ExpressModule;
// ...
}
// import { WebApp } from 'meteor/webapp';
Routes with WebApp and Express
To add Express routes in your app, check out the Express Guide and follow this example:
import { WebApp } from 'meteor/webapp';
const app = WebApp.express(); you can use as a normal express app
app.get('/hello', (req, res) => {
res.send('Hello World');
});
WebApp.handlers.use(app);
The code below is an example of how you can use the handlers
property to create a route in your app:
import { WebApp } from 'meteor/webapp';
WebApp.handlers.get('/hello', (req, res) => {
res.send('Hello World');
});
Middlewares with WebApp and Express
To include Router-level Express middleware in your app, check out the Express Guide and follow this example:
import { WebApp } from 'meteor/webapp';
const app = WebApp.express();
const router = WebApp.express.Router();
// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
console.log('Router-level - Time:', Date.now());
next();
})
// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
console.log('Router-level - Request URL:', req.originalUrl);
next();
}, (req, res, next) => {
console.log('Router-level - Request Type:', req.method);
next();
})
// mount the router on the app
app.use('/', router);
WebApp.handlers.use(app);
To include Application-level Express middleware in your app, check out the Express Guide and follow this example:
import { WebApp } from 'meteor/webapp';
const app = WebApp.express();
const router = WebApp.express.Router()
// This middleware is executed every time the app receives a request
router.use((req, res, next) => {
console.log('Router-level - Time:', Date.now());
next();
})
// This middleware shows request info for any type of HTTP request to the /hello/:name path
router.use('/hello/:name', (req, res, next) => {
console.log('Router-level - Request URL:', req.originalUrl);
next();
}, (req, res, next) => {
console.log('Router-level - Request Type:', req.method);
next();
})
// mount the router on the app
app.use('/', router);
WebApp.handlers.use(app);
New API Names
Having switched from Connect to Express, we updated API names to align with Express. See the details below:
WebApp.connectHandlers.use(middleware)
is nowWebApp.handlers.use(middleware)
WebApp.rawConnectHandlers.use(middleware)
is nowWebApp.rawHandlers.use(middleware)
WebApp.connectApp
is nowWebApp.expressApp
A few methods from WebApp internals are now async:
WebAppInternals.reloadClientPrograms()
WebAppInternals.pauseClient()
WebAppInternals.generateClientProgram()
WebAppInternals.generateBoilerplate()
WebAppInternals.setInlineScriptsAllowed()
WebAppInternals.enableSubresourceIntegrity()
WebAppInternals.setBundledJsCssUrlRewriteHook()
WebAppInternals.setBundledJsCssPrefix()
WebAppInternals.getBoilerplate
Meteor.userAsync
You should use Meteor.userAsync
instead of Meteor.user
in your code, especially if you want isomorphism or want to get your user in the server.
// Before
const user = Meteor.user();
// After
const user = await Meteor.userAsync();
Environment Variables
Meteor provides the Meteor.EnvironmentVariable
class, which helps maintain context across different boundaries.
With Meteor 3.0, we added support for asynchronous flows and improved how context is managed. As a result, some packages began losing context data, as described in this issue.
If your app or package uses EnvironmentVariable
, make sure to use EnvironmentVariable.withValue
at the top level to correctly preserve and propagate the context.
For instance, when updating publish behavior and introducing a new EnvironmentVariable
context, you need to adjust your code as follows:
const _publishConnectionId = new Meteor.EnvironmentVariable<
string | undefined
>();
// Before
function patchPublish(publish: typeof Meteor.publish) {
return function (this: typeof Meteor, name, func, ...args) {
return publish.call(
this,
name,
function (...args) {
return _publishConnectionId.withValue(this?.connection?.id, () =>
func.apply(this, args),
);
},
...args,
);
} as typeof Meteor.publish;
}
// After
function patchPublish(publish: typeof Meteor.publish) {
return function (this: typeof Meteor, name, func, ...args) {
return _publishConnectionId.withValue(this?.connection?.id, () => {
return publish.call(
this,
name,
function (...args) {
return func.apply(this, args);
},
...args,
);
});
} as typeof Meteor.publish;
}
This example demonstrates the migration applied to the universe:i18n
package.