What is miniflare?
Miniflare is a simulator for Cloudflare Workers, allowing you to develop and test your Workers locally. It provides a local environment that mimics the Cloudflare Workers runtime, enabling you to test your code without deploying it to the cloud.
What are miniflare's main functionalities?
Local Development
Miniflare allows you to run and test your Cloudflare Workers locally. This example demonstrates how to create a simple worker that responds with 'Hello from Miniflare!' to any request.
const { Miniflare } = require('miniflare');
const mf = new Miniflare({
script: `addEventListener('fetch', event => {
event.respondWith(new Response('Hello from Miniflare!'));
})`
});
(async () => {
const res = await mf.dispatchFetch('http://localhost');
console.log(await res.text()); // Hello from Miniflare!
})();
KV Storage Simulation
Miniflare can simulate Cloudflare Workers KV storage, allowing you to test interactions with KV namespaces locally. This example shows how to store and retrieve a value from a KV namespace.
const { Miniflare } = require('miniflare');
const mf = new Miniflare({
script: `addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const value = await MY_KV_NAMESPACE.get('key');
return new Response(value);
}`,
kvNamespaces: ['MY_KV_NAMESPACE']
});
(async () => {
await mf.getKVNamespace('MY_KV_NAMESPACE').put('key', 'value');
const res = await mf.dispatchFetch('http://localhost');
console.log(await res.text()); // value
})();
Durable Objects Simulation
Miniflare supports simulating Durable Objects, which are used for stateful logic in Cloudflare Workers. This example demonstrates a simple counter Durable Object that increments its value with each request.
const { Miniflare } = require('miniflare');
const mf = new Miniflare({
script: `class Counter {
constructor(state, env) {
this.state = state;
this.env = env;
}
async fetch(request) {
let value = (await this.state.storage.get('value')) || 0;
value++;
await this.state.storage.put('value', value);
return new Response(value);
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const id = 'counter';
const stub = await COUNTER.get(id);
return stub.fetch(request);
}`,
durableObjects: { COUNTER: 'Counter' }
});
(async () => {
const res1 = await mf.dispatchFetch('http://localhost');
console.log(await res1.text()); // 1
const res2 = await mf.dispatchFetch('http://localhost');
console.log(await res2.text()); // 2
})();
Other packages similar to miniflare
wrangler
Wrangler is the official CLI tool for managing Cloudflare Workers. It allows you to develop, test, and deploy Workers, but unlike Miniflare, it does not provide a local simulation environment. Instead, it focuses on deployment and management tasks.
worktop
Worktop is a lightweight framework for building Cloudflare Workers. It provides utilities for routing, handling requests, and managing responses. While it simplifies the development of Workers, it does not offer the local simulation capabilities that Miniflare provides.
๐ฅ Miniflare (WIP)
Fun, fully-local Cloudflare Workers simulator for developing and testing Workers
Features
- ๐ฆ KV (with optional persistence)
- โจ Cache (with optional persistence)
- ๐ Workers Sites
- ๐จ Fetch Events (with HTTP server and manual triggering)
- โฐ Scheduled Events (with manual and cron triggering)
- ๐
.env
File Support (for secrets) - ๐บ Source Map Support
- ๐ Automatic Reload on File Changes
- ๐ช Written in TypeScript
Coming Soon
- ๐ Durable Objects
- โ๏ธ WebSockets
- ๐ HTMLRewriter
- ๐ Custom Builds Support
- โ๏ธ WebAssembly Support
- ๐คน Custom Jest Environment
- โ
More Tests
CLI Usage
Usage: miniflare <script> [options]
Options:
-h, --help Show help [boolean]
-v, --version Show version number [boolean]
-p, --port HTTP server port (8787 by default) [number]
-d, --debug Log debug messages [boolean]
-c, --wrangler-config Path to wrangler.toml [string]
--wrangler-env Environment in wrangler.toml to use [string]
-w, --watch Watch files for changes [boolean]
-u, --upstream URL of upstream origin [string]
-t, --cron Cron pattern to trigger scheduled events with [array]
-k, --kv KV namespace to bind [array]
--kv-persist Path to persist KV data to (omit path for default)
--cache-persist Path to persist cached data to (omit path for default)
-s, --site Path to serve Workers Site files from [string]
--site-include Glob pattern of site files to serve [array]
--site-exclude Glob pattern of site files not to serve [array]
-e, --env Path to .env file [string]
-b, --binding Bind variable/secret (KEY=VALUE) [array]
<script>
should be a path to a pre-bundled Worker.
If you're using Webpack for instance, set this to your output file.
Use --debug
/-d
to see additional log messages including processed options and watched files. (recommended)
Use --wrangler-config <toml_path>
/-c <toml_path>
to load options for KV, cache, etc from a wrangler.toml
file. (recommended)
You can also include an additional [miniflare]
section for Miniflare specific configuration:
[miniflare]
upstream = "https://mrbbot.dev"
kv_persist = true
cache_persist = true
env_path = ".env"
port = 8787
KV and cache persistence can be enabled with the --kv-persist
and --cache-persist
flags respectively.
Including these on their own will store KV and Cache data in the .mf
directory.
Optionally, you can specify a path (e.g. --kv-persist ./data
) to use a different location.
Programmatic Usage
import vm from "vm";
import { ConsoleLog, Miniflare, Request } from "miniflare";
const mf = new Miniflare("./path/to/script.js", {
sourceMap: true,
log: new ConsoleLog(),
wranglerConfigPath: "wrangler.toml",
watch: true,
port: 8787,
upstream: "https://mrbbot.dev",
crons: ["0 * * * *"],
kvNamespaces: ["TEST_NAMESPACE"],
kvPersist: true,
cachePersist: "./data/",
sitePath: "./public/",
envPath: ".env",
});
const mf = new Miniflare(
new vm.Script(`
addEventListener("fetch", (event) => {
event.respondWith(handleRequest(event.request));
event.waitUntil(Promise.resolve("Something"));
});
async function handleRequest(request) {
const value = await TEST_NAMESPACE.get("key");
return new Response(\`Hello from Miniflare! key="\${value}"\`, {
headers: { "content-type": "text/plain" },
})
}
addEventListener("scheduled", (event) => {
event.waitUntil(Promise.resolve("Something else"));
});
`),
{
kvNamespaces: ["TEST_NAMESPACE"],
log: new ConsoleLog(),
}
);
const ns = await mf.getNamespace("TEST_NAMESPACE");
await ns.put("key", "testing");
const cache = await mf.getCache();
const cachedRes = await cache.match(new Request("http://localhost"));
const res = await mf.dispatchFetch(new Request("http://localhost"));
const body = await res.text();
console.log(body);
const waitUntil = await res.waitUntil();
console.log(waitUntil[0]);
mf.createServer().listen(3000);
const waitUntil2 = await mf.dispatchScheduled(Date.now());
console.log(waitUntil2[0]);
Acknowledgements
Many thanks to dollarshaveclub/cloudworker and gja/cloudflare-worker-local for inspiration.