A dedicated, production-ready Server-Sent Events (SSE) library for Nim - A framework-agnostic implementation of the HTML5 SSE protocol.
Server-Sent Events are perfect for:
- π€ LLM API Streaming - OpenAI, Anthropic, and other AI APIs use SSE for token streaming
- π Real-time Updates - Stock prices, news feeds, live scores
- π Notifications - Push notifications from server to browser
- π Progress Tracking - File upload/download progress, build status
- ποΈ Live Comments - Live chat, collaborative editing
- β Full SSE Specification - Compliant with HTML5 SSE standard
- β Streaming Parser - Process SSE data in real-time with chunked transfer
- β Reconnection Support - Automatic Last-Event-ID tracking for resume capability
- β Security Built-in - DoS protection, field injection prevention, buffer limits
- β Minimal Dependencies - Pure Nim, uses only Nim standard library (no external dependencies)
- β High Performance - Optimized for efficient event processing
- β Thread Safety Documented - Clear thread safety documentation for all APIs
- β JSON Support - Easy serialization/deserialization of events
- β Comprehensive Examples - 13 example files covering all use cases
nimble install server_sent_eventsgit clone https://github.com/iceberg-work/sse.git
cd sse
nimble installNote: The package is installed as server_sent_events, but you import it in your code as sse (the module filename).
Add to your .nimble file:
requires "server_sent_events >= 0.2.0"import sse
# Create an event
let evt = initSSEvent("Hello, World!", "message", "1", 5000)
# Format for sending
echo evt.format()
# Output:
# event: message
# data: Hello, World!
# id: 1
# retry: 5000
# import sse
let events = parse("data: hello\n\ndata: world\n\n")
for evt in events:
echo evt.data # "hello", then "world"import sse
var parser = initSSEParser()
# Feed data in chunks
let events1 = parser.feed("data: Hel")
let events2 = parser.feed("lo\n\n")
echo events2[0].data # "Hello"nim c -r examples/run_all.nim| Example | Description | Run Command |
|---|---|---|
| Quick Start | 5-minute introduction | nim c -r examples/quick_start.nim |
| Basic Usage | Core API | nim c -r examples/basic_usage.nim |
| HTTP Server | SSE server with asynchttpserver | nim c -r examples/http_server_examples.nim |
| HTTP Client | Consume SSE streams | nim c -r examples/http_client_examples.nim |
| LLM API | OpenAI-style streaming | See http_client_examples.nim Scenario 3 |
| Security | DoS protection, injection prevention | nim c -r examples/security_examples.nim |
| Consumer | SSE client consumption patterns | nim c -r examples/consumer_examples.nim |
| JSON | JSON serialization/deserialization | nim c -r examples/json_examples.nim |
| Parser | Streaming parser usage | nim c -r examples/parser_examples.nim |
| Web Framework | Prologue, Jester, Karax integration | nim c -r examples/web_framework_integration.nim |
π Full example guide: examples/README.md
proc initSSEvent*(data: string, event = "", id = "", retry = -1): SSEventproc format*(evt: SSEvent): string
proc format*(events: seq[SSEvent]): string
proc formatHeartbeat*(comment = ""): stringproc parse*(raw: string, maxSize = DefaultMaxParseSize): seq[SSEvent]
proc feed*(parser: var SSEParser, data: string): seq[SSEvent]proc validateSyntax*(raw: string): tuple[valid: bool, error: string]
proc validateStrict*(raw: string): tuple[valid: bool, error: string]proc toJson*(evt: SSEvent): JsonNode
proc fromJson*(node: JsonNode): SSEventπ Full API documentation: Generate with nimble docs or view docs/sse.html
import sse, httpclient, json, strutils
var client = newHttpClient()
client.headers = newHttpHeaders({
"Authorization": "Bearer YOUR_API_KEY",
"Accept": "text/event-stream"
})
# β οΈ WARNING: For production, use getStream() instead of response.body
# to avoid loading entire streams into memory. See http_client_examples.nim.
let response = client.get("https://api.openai.com/v1/chat/completions?stream=true")
var parser = initSSEParser()
for line in response.body.splitLines():
let events = parser.feed(line & "\n")
for evt in events:
if evt.data == "[DONE]":
break
let json = parseJson(evt.data)
echo json["choices"][0]["delta"]["content"]import asynchttpserver, sse, asyncdispatch
# Note: This example shows SSE format. For true streaming, use lower-level APIs.
proc handleSSE(req: Request) {.async.} =
try:
# Build SSE response
var response = ""
# Add events
for i in 1..10:
let evt = initSSEvent("Message " & $i, "update", $i)
response.add(evt.format())
# Send response with SSE headers
await req.respond(Http200, response, {
"Content-Type": "text/event-stream",
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}.newHttpHeaders)
except:
echo "Error handling request"
# Start server
var server = newAsyncHttpServer()
echo "SSE Server listening on http://127.0.0.1:8080"
discard serve(server, Port(8080), handleSSE, "127.0.0.1")
runForever()import sse
# Create notification event
let notification = initSSEvent(
"""{"title": "New Message", "body": "You have a new message!"}""",
"notification",
"msg-123"
)
# Send to client
echo notification.format()The library includes built-in protections against common attacks:
- DoS Protection: Default 10MB parse limit, configurable buffer sizes (default: 1MB)
- Field Injection Prevention: Newlines (
\r,\n,\u2028,\u2029) ineventandidfields are sanitized - Buffer Overflow: Parser raises
SSEErrorwhen buffer limit exceeded - Invalid Retry: Negative retry values are silently ignored
Important: Understand the scope of protection:
-
Data Field Not Sanitized: The
datafield preserves newlines (as per SSE spec). Be cautious when embedding JSON or other structured data. -
JSON Serialization:
toJson()outputs raw values - do NOT embed resulting JSON directly in HTML/JavaScript without escaping. -
Streaming Memory: When consuming streams, always use streaming APIs (
getStream()) instead ofresponse.bodyto avoid memory exhaustion.
See examples/security_examples.nim for details and SECURITY.md for comprehensive security guide.
While other HTTP libraries may include basic SSE support, this library is dedicated to SSE:
| Feature | This SSE Library | General HTTP Libraries |
|---|---|---|
| SSE Protocol Support | β Full | |
| Streaming Parser | β Built-in | β DIY |
| Reconnection (Last-Event-ID) | β Automatic | β Manual |
| Security Features | β Comprehensive | |
| Documentation | β Extensive | |
| Examples | β 13 files | |
| Dependencies | β Zero | Varies |
Complementary, not competitive: Use this library alongside your favorite HTTP server/client for the best SSE experience.
- Framework Agnostic - Works with any HTTP library
- Zero Dependencies - Pure Nim, no external requirements
- Security First - Built-in protections against common attacks
- Performance - Optimized for high-throughput scenarios
- Developer Experience - Comprehensive docs and examples
# Run unit tests
nimble test
# Run all examples
nim c -r examples/run_all.nim
# Run benchmarks
nim c -r -d:release tests/benchmark.nimThis library is optimized for high performance with efficient memory usage. Actual results vary by hardware and use case.
- Event Creation: Very fast (typically < 100ns for simple events)
- Formatting: Sub-microsecond for simple events
- Parsing: High throughput for standard SSE data
- Memory: Bounded by buffer size (default: 1MB for parser, 10MB for parse())
Important: Always benchmark with your specific workload and hardware:
# Quick benchmark (release mode for accurate results)
nimble bench
# Or run directly
nim c -r -d:release tests/benchmark.nimThe benchmark suite tests:
- Event creation speed
- Formatting performance (simple, multiline, large data)
- Parsing throughput
- Round-trip efficiency
- Memory usage for large data (1MB+)
Note: Performance numbers in documentation are estimates. Actual performance depends on:
- CPU speed and architecture
- Memory bandwidth
- Nim compiler version and optimization flags
- Event size and complexity
Contributions are welcome! Feel free to open issues for bugs, suggestions for improvements, or pull requests with fixes.
MIT License - See LICENSE file for details.
Made with β€οΈ for the Nim community
If you find this library useful, please consider giving it a star β