A complete example demonstrating the new high-level messaging API from @nostr-dev-kit/messages that simplifies NIP-17 private direct message implementation.
This example uses the new @nostr-dev-kit/messages package instead of implementing NIP-17 manually:
// Manual rumor creation
const rumor = new NDKEvent(ndk);
rumor.kind = 14;
rumor.content = message;
rumor.pubkey = sender.pubkey; // Must manually set
rumor.tags = [["p", recipient.pubkey]];
// Manual gift wrapping
const wrapped = await giftWrap(rumor, recipient, signer);
// Manual relay management
const relays = await getRecipientDMRelays(recipient);
const relaySet = NDKRelaySet.fromRelayUrls(relays, ndk);
await wrapped.publish(relaySet);// Simple one-liner
await messenger.sendMessage(recipient, "Hello!");
// Or with conversation context
const conversation = await messenger.getConversation(recipient);
await conversation.sendMessage("Hello!");- ✨ Simple API: One-line message sending
- 💬 Conversation Management: Automatic grouping and threading
- 📡 Event-Driven: Real-time updates via EventEmitter
- 💾 Storage Abstraction: Pluggable storage adapters
- 🔄 Automatic Relay Discovery: Handles kind 10050 relay lists
- 🎯 Protocol Abstraction: Same API for future MLS support
From the monorepo root:
# Install dependencies
bun install
# Build the messages package
cd messages && bun run build && cd ..
# Navigate to example
cd messages/examples/nip-17-chatbunx tsx generate-keys.tsThis creates two test identities (Alice and Bob) with their nsec/npub pairs.
bunx tsx src/index.ts send <your-nsec> <recipient-npub> "Your message"bunx tsx src/index.ts list <your-nsec>Shows all conversations with unread counts and message previews.
bunx tsx src/index.ts read <your-nsec> <other-npub>Displays the full conversation and marks messages as read.
bunx tsx src/index.ts listen <your-nsec> [duration-seconds]Real-time subscription to incoming messages (default: 30 seconds).
bunx tsx src/index.ts relay-list <your-nsec> wss://relay1.com wss://relay2.comPublishes your preferred DM relays (kind 10050).
# 1. Generate keys
bunx tsx generate-keys.ts
# 2. Alice sends a message to Bob
bunx tsx src/index.ts send <alice-nsec> <bob-npub> "Hey Bob! Using the new messaging API!"
# 3. Bob listens for messages
bunx tsx src/index.ts listen <bob-nsec>
# 4. Bob reads the conversation
bunx tsx src/index.ts read <bob-nsec> <alice-npub>
# 5. Bob replies
bunx tsx src/index.ts send <bob-nsec> <alice-npub> "Hi Alice! This API is so much cleaner!"
# 6. Alice checks her conversations
bunx tsx src/index.ts list <alice-nsec>core/examples/nip-17-dms/
├── dm-manager.ts # 300 lines - Manual NIP-17 implementation
├── storage.ts # 100 lines - Custom storage
└── index.ts # 400 lines - CLI with all logic
messages/examples/nip-17-chat/
├── file-storage.ts # 100 lines - Storage adapter implementation
└── index.ts # 300 lines - Clean CLI using the library
The new library handles:
- Gift wrapping complexity
- Relay discovery and management
- Subscription handling
- Message deduplication
- Conversation grouping
- Event emissions
The new API is fully event-driven:
// Global messenger events
messenger.on('message', (message) => {
console.log('New message:', message);
});
messenger.on('conversation-created', (conversation) => {
console.log('New conversation started');
});
// Conversation-specific events
conversation.on('message', (message) => {
updateUI(message);
});
conversation.on('error', (error) => {
showError(error);
});This example uses a file-based storage adapter, but you can easily swap it:
import { MemoryAdapter } from '@nostr-dev-kit/messages/storage';
// Use in-memory storage (no persistence)
const messenger = new NDKMessenger(ndk, {
storage: new MemoryAdapter()
});
// Or implement your own (SQLite, IndexedDB, etc.)
class MyCustomAdapter implements StorageAdapter {
// ... implementation
}- Simplicity: Focus on your app logic, not protocol details
- Maintainability: Updates to NIP-17 happen in the library
- Consistency: Same patterns across all NDK packages
- Future-Proof: Ready for MLS when NIP-EE is implemented
- Type Safety: Full TypeScript with strict types
- Testing: Easier to mock and test high-level APIs
- The library will soon support NIP-EE (MLS-based messaging)
- Same API will work for both protocols
- Automatic protocol selection based on recipient capabilities
- Group messaging support coming with MLS
MIT