Skip to content

Commit 23ec47d

Browse files
committed
Add get started back
1 parent 58f0dd6 commit 23ec47d

File tree

3 files changed

+403
-6
lines changed

3 files changed

+403
-6
lines changed
Lines changed: 393 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,393 @@
1+
---
2+
order: 1
3+
title: Get Started
4+
---
5+
6+
The Miniflare API allows you to dispatch events to workers without making actual HTTP requests, simulate connections between Workers, and interact with local emulations of storage products like [KV](/storage/kv), [R2](/storage/r2), and [Durable Objects](/storage/durable-objects). This makes it great for writing tests, or other advanced use cases where you need finer-grained control.
7+
8+
## Installation
9+
10+
Miniflare is installed using `npm` as a dev dependency:
11+
12+
```sh
13+
$ npm install -D miniflare
14+
```
15+
16+
## Usage
17+
18+
In all future examples, we'll assume Node.js is running in ES module mode. You
19+
can do this by setting the `type` field in your `package.json`:
20+
21+
```json title=package.json
22+
{
23+
...
24+
"type": "module"
25+
...
26+
}
27+
```
28+
29+
To initialise Miniflare, import the `Miniflare` class from `miniflare`:
30+
31+
```js
32+
import { Miniflare } from "miniflare";
33+
34+
const mf = new Miniflare({
35+
modules: true,
36+
script: `
37+
export default {
38+
async fetch(request, env, ctx) {
39+
return new Response("Hello Miniflare!");
40+
}
41+
}
42+
`,
43+
});
44+
45+
const res = await mf.dispatchFetch("http://localhost:8787/");
46+
console.log(await res.text()); // Hello Miniflare!
47+
await mf.dispose();
48+
```
49+
50+
The [rest of these docs](/core/fetch) go into more detail on configuring
51+
specific features.
52+
53+
### String and File Scripts
54+
55+
Note in the above example we're specifying `script` as a string. We could've
56+
equally put the script in a file such as `worker.js`, then used the `scriptPath`
57+
property instead:
58+
59+
```js
60+
const mf = new Miniflare({
61+
scriptPath: "worker.js",
62+
});
63+
```
64+
65+
### Watching, Reloading and Disposing
66+
67+
Miniflare's API is primarily intended for testing use cases, where file watching isn't usually required. If you need to watch files, consider using a separate file watcher like [fs.watch()](https://nodejs.org/api/fs.html#fswatchfilename-options-listener) or [chokidar](https://github.com/paulmillr/chokidar), and calling setOptions() with your original configuration on change.
68+
69+
To cleanup and stop listening for requests, you should `dispose()` your instances:
70+
71+
```js
72+
await mf.dispose();
73+
```
74+
75+
You can also manually reload scripts (main and Durable Objects') and options by calling `setOptions()` with the original configuration object.
76+
77+
### Updating Options and the Global Scope
78+
79+
You can use the `setOptions` method to update the options of an existing
80+
`Miniflare` instance. This accepts the same options object as the
81+
`new Miniflare` constructor, applies those options, then reloads the worker.
82+
83+
```js
84+
const mf = new Miniflare({
85+
script: "...",
86+
kvNamespaces: ["TEST_NAMESPACE"],
87+
bindings: { KEY: "value1" },
88+
});
89+
90+
await mf.setOptions({
91+
script: "...",
92+
kvNamespaces: ["TEST_NAMESPACE"],
93+
bindings: { KEY: "value2" },
94+
});
95+
```
96+
97+
### Dispatching Events
98+
99+
`getWorker` dispatches `fetch`, `queues`, and `scheduled` events
100+
to workers respectively:
101+
102+
```js
103+
import { Miniflare } from "miniflare";
104+
105+
const mf = new Miniflare({
106+
script: `
107+
export default {
108+
let lastScheduledController;
109+
let lastQueueBatch;
110+
async fetch(request, env, ctx) {
111+
const { pathname } = new URL(request.url);
112+
if (pathname === "/scheduled") {
113+
return Response.json({
114+
scheduledTime: lastScheduledController?.scheduledTime,
115+
cron: lastScheduledController?.cron,
116+
});
117+
} else if (pathname === "/queue") {
118+
return Response.json({
119+
queue: lastQueueBatch.queue,
120+
messages: lastQueueBatch.messages.map((message) => ({
121+
id: message.id,
122+
timestamp: message.timestamp.getTime(),
123+
body: message.body,
124+
bodyType: message.body.constructor.name,
125+
})),
126+
});
127+
} else if (pathname === "/get-url") {
128+
return new Response(request.url);
129+
} else {
130+
return new Response(null, { status: 404 });
131+
}
132+
},
133+
async scheduled(controller, env, ctx) {
134+
lastScheduledController = controller;
135+
if (controller.cron === "* * * * *") controller.noRetry();
136+
},
137+
async queue(batch, env, ctx) {
138+
lastQueueBatch = batch;
139+
if (batch.queue === "needy") batch.retryAll();
140+
for (const message of batch.messages) {
141+
if (message.id === "perfect") message.ack();
142+
}
143+
}
144+
}
145+
`,
146+
});
147+
148+
const res = await mf.dispatchFetch("http://localhost:8787/", {
149+
headers: { "X-Message": "Hello Miniflare!" },
150+
});
151+
console.log(await res.text()); // Hello Miniflare!
152+
153+
const scheduledResult = await worker.scheduled({
154+
cron: "* * * * *",
155+
});
156+
console.log(scheduledResult); // { outcome: "ok", noRetry: true });
157+
158+
const queueResult = await worker.queue("needy", [
159+
{ id: "a", timestamp: new Date(1000), body: "a" },
160+
{ id: "b", timestamp: new Date(2000), body: { b: 1 } },
161+
]);
162+
console.log(queueResult); // { outcome: "ok", retryAll: true, ackAll: false, explicitRetries: [], explicitAcks: []}
163+
```
164+
165+
See [📨 Fetch Events](/core/fetch) and [⏰ Scheduled Events](/core/scheduled)
166+
for more details.
167+
168+
### HTTP Server
169+
170+
Miniflare starts an HTTP server automatically. To wait for it to be ready, `await` the `ready` property:
171+
172+
```js {11}
173+
import { Miniflare } from "miniflare";
174+
175+
const mf = new Miniflare({
176+
modules: true,
177+
script: `
178+
export default {
179+
async fetch(request, env, ctx) {
180+
return new Response("Hello Miniflare!");
181+
})
182+
}
183+
`,
184+
port: 5000,
185+
});
186+
await mf.ready;
187+
console.log("Listening on :5000");
188+
```
189+
190+
#### `Request#cf` Object
191+
192+
By default, Miniflare will fetch the `Request#cf` object from a trusted
193+
Cloudflare endpoint. You can disable this behaviour, using the `cf` option:
194+
195+
```js
196+
const mf = new Miniflare({
197+
cf: false,
198+
});
199+
```
200+
201+
You can also provide a custom cf object via a filepath:
202+
203+
```js
204+
const mf = new Miniflare({
205+
cf: "cf.json",
206+
});
207+
```
208+
209+
### HTTPS Server
210+
211+
To start an HTTPS server instead, set the `https` option. To use the [default shared self-signed certificate](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare/src/http/cert.ts), set `https` to `true`:
212+
213+
```js
214+
const mf = new Miniflare({
215+
https: true,
216+
});
217+
```
218+
219+
To load an existing certificate from the file system:
220+
221+
```js
222+
const mf = new Miniflare({
223+
// These are all optional, you don't need to include them all
224+
httpsKeyPath: "./key.pem",
225+
httpsCertPath: "./cert.pem",
226+
});
227+
```
228+
229+
To load an existing certificate from strings instead:
230+
231+
```js
232+
const mf = new Miniflare({
233+
// These are all optional, you don't need to include them all
234+
httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
235+
httpsCert: "-----BEGIN CERTIFICATE-----...",
236+
});
237+
```
238+
239+
If both a string and path are specified for an option (e.g. `httpsKey` and
240+
`httpsKeyPath`), the string will be preferred.
241+
242+
### Logging
243+
244+
By default, `[mf:*]` logs are disabled when using the API. To
245+
enable these, set the `log` property to an instance of the `Log` class. Its only
246+
parameter is a log level indicating which messages should be logged:
247+
248+
```js {5}
249+
import { Miniflare, Log, LogLevel } from "miniflare";
250+
251+
const mf = new Miniflare({
252+
scriptPath: "worker.js",
253+
log: new Log(LogLevel.DEBUG), // Enable debug messages
254+
});
255+
```
256+
257+
## Reference
258+
259+
```js
260+
import { Miniflare, Log, LogLevel } from "miniflare";
261+
262+
const mf = new Miniflare({
263+
// All options are optional, but one of script or scriptPath is required
264+
265+
log: new Log(LogLevel.INFO), // Logger Miniflare uses for debugging
266+
267+
script: `
268+
export default {
269+
async fetch(request, env, ctx) {
270+
return new Response("Hello Miniflare!");
271+
}
272+
}
273+
`,
274+
scriptPath: "./index.js",
275+
276+
modules: true, // Enable modules
277+
modulesRules: [
278+
// Modules import rule
279+
{ type: "ESModule", include: ["**/*.js"], fallthrough: true },
280+
{ type: "Text", include: ["**/*.text"] },
281+
],
282+
compatibilityDate: "2021-11-23", // Opt into backwards-incompatible changes from
283+
compatibilityFlags: ["formdata_parser_supports_files"], // Control specific backwards-incompatible changes
284+
upstream: "https://miniflare.dev", // URL of upstream origin
285+
workers: [{
286+
// reference additional named workers
287+
name: "worker2",
288+
kvNamespaces: { COUNTS: "counts" },
289+
serviceBindings: {
290+
INCREMENTER: "incrementer",
291+
// Service bindings can also be defined as custom functions, with access
292+
// to anything defined outside Miniflare.
293+
async CUSTOM(request) {
294+
// `request` is the incoming `Request` object.
295+
return new Response(message);
296+
},
297+
},
298+
modules: true,
299+
script: `export default {
300+
async fetch(request, env, ctx) {
301+
// Get the message defined outside
302+
const response = await env.CUSTOM.fetch("http://host/");
303+
const message = await response.text();
304+
305+
// Increment the count 3 times
306+
await env.INCREMENTER.fetch("http://host/");
307+
await env.INCREMENTER.fetch("http://host/");
308+
await env.INCREMENTER.fetch("http://host/");
309+
const count = await env.COUNTS.get("count");
310+
311+
return new Response(message + count);
312+
}
313+
}`,
314+
},
315+
}],
316+
name: "worker", // Name of service
317+
routes: ["*site.mf/worker"],
318+
319+
320+
host: "127.0.0.1", // Host for HTTP(S) server to listen on
321+
port: 8787, // Port for HTTP(S) server to listen on
322+
https: true, // Enable self-signed HTTPS (with optional cert path)
323+
httpsKey: "-----BEGIN RSA PRIVATE KEY-----...",
324+
httpsKeyPath: "./key.pem", // Path to PEM SSL key
325+
httpsCert: "-----BEGIN CERTIFICATE-----...",
326+
httpsCertPath: "./cert.pem", // Path to PEM SSL cert chain
327+
cf: "./node_modules/.mf/cf.json", // Path for cached Request cf object from Cloudflare
328+
liveReload: true, // Reload HTML pages whenever worker is reloaded
329+
330+
331+
332+
kvNamespaces: ["TEST_NAMESPACE"], // KV namespace to bind
333+
kvPersist: "./kv-data", // Persist KV data (to optional path)
334+
335+
r2Buckets: ["BUCKET"], // R2 bucket to bind
336+
r2Persist: "./r2-data", // Persist R2 data (to optional path)
337+
338+
durableObjects: {
339+
// Durable Object to bind
340+
TEST_OBJECT: "TestObject", // className
341+
API_OBJECT: { className: "ApiObject", scriptName: "api" },
342+
},
343+
durableObjectsPersist: "./durable-objects-data", // Persist Durable Object data (to optional path)
344+
345+
cache: false, // Enable default/named caches (enabled by default)
346+
cachePersist: "./cache-data", // Persist cached data (to optional path)
347+
cacheWarnUsage: true, // Warn on cache usage, for workers.dev subdomains
348+
349+
sitePath: "./site", // Path to serve Workers Site files from
350+
siteInclude: ["**/*.html", "**/*.css", "**/*.js"], // Glob pattern of site files to serve
351+
siteExclude: ["node_modules"], // Glob pattern of site files not to serve
352+
353+
354+
bindings: { SECRET: "sssh" }, // Binds variable/secret to environment
355+
wasmBindings: { ADD_MODULE: "./add.wasm" }, // WASM module to bind
356+
textBlobBindings: { TEXT: "./text.txt" }, // Text blob to bind
357+
dataBlobBindings: { DATA: "./data.bin" }, // Data blob to bind
358+
});
359+
360+
await mf.setOptions({ kvNamespaces: ["TEST_NAMESPACE2"] }); // Apply options and reload
361+
362+
const bindings = await mf.getBindings(); // Get bindings (KV/Durable Object namespaces, variables, etc)
363+
364+
// Dispatch "fetch" event to worker
365+
const res = await mf.dispatchFetch("http://localhost:8787/", {
366+
headers: { Authorization: "Bearer ..." },
367+
});
368+
const text = await res.text();
369+
370+
// Dispatch "scheduled" event to worker
371+
const scheduledResult = await worker.scheduled({ cron: "30 * * * *" })
372+
373+
const TEST_NAMESPACE = await mf.getKVNamespace("TEST_NAMESPACE");
374+
375+
const BUCKET = await mf.getR2Bucket("BUCKET");
376+
377+
const caches = await mf.getCaches(); // Get global `CacheStorage` instance
378+
const defaultCache = caches.default;
379+
const namedCache = await caches.open("name");
380+
381+
// Get Durable Object namespace and storage for ID
382+
const TEST_OBJECT = await mf.getDurableObjectNamespace("TEST_OBJECT");
383+
const id = TEST_OBJECT.newUniqueId();
384+
const storage = await mf.getDurableObjectStorage(id);
385+
386+
// Get Queue Producer
387+
const producer = await mf.getQueueProducer("QUEUE_BINDING");
388+
389+
// Get D1 Database
390+
const db = await mf.getD1Database("D1_BINDING")
391+
392+
await mf.dispose(); // Cleanup storage database connections and watcher
393+
```

0 commit comments

Comments
 (0)