Follow this tutorial to run a local XMTP backend and send a message from one
client to another.
Each client uses a key pair to create an identity and register it with the
backend. This gives the client an inbox ID, which is a stable identifier that
other clients send messages to.
You need Docker with Docker Compose. For
Browser and Node, you also need Node.js 22.18.0 or later.
The last command returns when the backend is healthy. The backend URL is
http://127.0.0.1:5050. Use 127.0.0.1, not localhost: some systems resolve
localhost to an IPv6 address that Docker does not forward.
The Compose file
compose.yaml
# Local XMTP backend for the quickstart. Do not use it in production:
# it has no authentication and a fixed database password.
#
# docker compose up -d --wait start, and wait until the backend is healthy
# docker compose logs backend show the backend logs
# docker compose down stop, and keep the data
# docker compose down -v stop, and delete the data
Replace the contents of src/main.ts with this code. Each page load creates a
new client. A page opened with ?to=<inbox-id> sends one message to that inbox.
src/main.ts
import {
classClient
An XMTP client. Its inputs and results are public values; the binding
Client and the target transport stay private to the package.
Client } from"@xmtp/browser-sdk";
const
constlog: (line:string) =>void
log= (
line: string
line:string) => {
var document:Document
window.document returns a reference to the document contained in the window.
In an HTML document, the document.createElement() method creates the HTML element specified by localName, or an HTMLUnknownElement if localName isn't recognized.
The search property of the Location interface is a search string, also called a query string, that is a string containing a "?" followed by the parameters of the location's URL. If the URL does not have a search query, this property contains an empty string, "".
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker threads.
In other words, the following example would not work:
Assigning a property on process.env will implicitly convert the value
to a string. This behavior is deprecated. Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from'node:process';
env.test =null;
console.log(env.test);
// => 'null'
env.test =undefined;
console.log(env.test);
// => 'undefined'
Use delete to delete a property from process.env.
import { env } from'node:process';
env.TEST=1;
delete env.TEST;
console.log(env.TEST);
// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from'node:process';
env.TEST=1;
console.log(env.test);
// => 1
Unless explicitly specified when creating a Worker instance,
each Worker thread has its own copy of process.env, based on its
parent thread's process.env, or whatever was specified as the env option
to the Worker constructor. Changes to process.env will not be visible
across Worker threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner
unlike the main thread.
@since ― v0.1.27
env.
string |undefined
XMTP_BACKEND_URL??"http://127.0.0.1:5050" },
storage: StorageOptions
storage: {
location: StorageLocation
location: "inMemory" },
});
var console:Console
console.
Console.log(...data: any[]): void
The console.log() static method outputs a message to the console.
Create send.ts. It creates a second client and sends one message to the inbox
ID you pass to it:
send.ts
import {
classClient
An XMTP client. Its inputs and results are public values; the binding
Client and the target transport stay private to the package.
Client } from"@xmtp/node-sdk";
const [
constinboxId:string
inboxId,
consttext:string
text="Hello from XMTP"] =
var process:NodeJS.Process
process.
NodeJS.Process.argv: string[]
The process.argv property returns an array containing the command-line
arguments passed when the Node.js process was launched. The first element will
be
execPath
. See process.argv0 if access to the original value
of argv[0] is needed. The second element will be the path to the JavaScript
file being executed. If a program entry point was provided, the second element
will be the absolute path to it. The remaining elements are additional command-line
arguments.
For example, assuming the following script for process-args.js:
Returns a copy of a section of an array.
For both start and end, a negative index can be used to indicate an offset from the end of the array.
For example, -2 refers to the second to last element of the array.
@param ― start The beginning index of the specified portion of the array.
If start is undefined, then the slice begins at index 0.
@param ― end The end index of the specified portion of the array. This is exclusive of the element at the index 'end'.
If end is undefined, then the slice extends to the end of the array.
slice(2);
if (!
constinboxId:string
inboxId) thrownew
var Error:ErrorConstructor
new (message?:string, options?:ErrorOptions) =>Error (+1 overload)
It is possible to modify this object, but such modifications will not be
reflected outside the Node.js process, or (unless explicitly requested)
to other Worker threads.
In other words, the following example would not work:
Assigning a property on process.env will implicitly convert the value
to a string. This behavior is deprecated. Future versions of Node.js may
throw an error when the value is not a string, number, or boolean.
import { env } from'node:process';
env.test =null;
console.log(env.test);
// => 'null'
env.test =undefined;
console.log(env.test);
// => 'undefined'
Use delete to delete a property from process.env.
import { env } from'node:process';
env.TEST=1;
delete env.TEST;
console.log(env.TEST);
// => undefined
On Windows operating systems, environment variables are case-insensitive.
import { env } from'node:process';
env.TEST=1;
console.log(env.test);
// => 1
Unless explicitly specified when creating a Worker instance,
each Worker thread has its own copy of process.env, based on its
parent thread's process.env, or whatever was specified as the env option
to the Worker constructor. Changes to process.env will not be visible
across Worker threads, and only the main thread can make changes that
are visible to the operating system or to native add-ons. On Windows, a copy of process.env on a Worker instance operates in a case-sensitive manner
unlike the main thread.
End the client. Messages from it then fail with ClientClosed. Ending a
closed client does nothing, also after its browser worker stopped.
end();
The signer provides identity(), kind(), and sign(request).
SDKClient.generateKeys() returns a local signing key that implements these
methods. This example creates two temporary clients on the same backend, sends a
message from one to the other, and streams messages. Replace the backend URL
with a URL that your device can reach.
The withDemoClients helper ends both clients when its block exits.
The signer provides identity(), kind(), and sign(request).
SDKClient.generateKeys() returns a local signing key that implements these
methods. This example creates two temporary clients on the same backend, sends a
message from one to the other, and streams messages. Replace the backend URL
with a URL that your device can reach.
Load each client’s stored 32-byte database key before the calls below. Generate a
new key securely for a new database, store it securely, and reuse it on reopen.
Omitting the key selects unencrypted storage.
streamAllMessages() delivers messages from every conversation, including
conversations that start after the stream opens. By default, it includes
allowed and unknown conversations. A conversation that another inbox
starts is unknown until you allow it. See Consent.
When the stream opens, it first delivers the messages that arrived while the
client was offline.
These examples use randomly generated keys that get thrown away as soon as the
process ends, and in-memory databases. Each run is a new inbox. A real app must
save and restore its signing key
and keep the same database between launches. In Node, pass a 32-byte
storage.encryptionKey to encrypt the database.