{"id":25558094,"url":"https://github.com/cablehead/http-nu","last_synced_at":"2026-01-05T00:20:18.670Z","repository":{"id":275026430,"uuid":"924796671","full_name":"cablehead/http-nu","owner":"cablehead","description":"attach Nushell scripts to a HTTP interface","archived":false,"fork":false,"pushed_at":"2025-04-09T18:21:26.000Z","size":245,"stargazers_count":9,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-09T18:46:19.458Z","etag":null,"topics":["cli","http-server","nushell"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cablehead.png","metadata":{"files":{"readme":"README.md","changelog":"changes/v0.2.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-30T17:05:49.000Z","updated_at":"2025-04-09T18:21:30.000Z","dependencies_parsed_at":"2025-01-30T19:36:52.844Z","dependency_job_id":"c47ecdcb-3930-4849-ba61-0898354b8955","html_url":"https://github.com/cablehead/http-nu","commit_stats":null,"previous_names":["cablehead/http-nu"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fhttp-nu","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fhttp-nu/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fhttp-nu/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cablehead%2Fhttp-nu/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cablehead","download_url":"https://codeload.github.com/cablehead/http-nu/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248507471,"owners_count":21115608,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","http-server","nushell"],"created_at":"2025-02-20T15:29:29.761Z","updated_at":"2026-01-05T00:20:18.656Z","avatar_url":"https://github.com/cablehead.png","language":"Rust","readme":"\u003c!-- LOGO --\u003e\n\u003ch1\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://http-nu.cross.stream\"\u003e\n    \u003cimg alt=\"http-nu\" src=\"./www/assets/og.png\" /\u003e\n  \u003c/a\u003e\n  \u003cbr\u003e\u003cbr\u003e\n  http-nu\n\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n    The surprisingly performant, \u003ca href=\"https://data-star.dev\"\u003eDatastar\u003c/a\u003e-ready, \u003ca href=\"https://www.nushell.sh\"\u003eNushell\u003c/a\u003e-scriptable HTTP server that fits in your back pocket.\n    \u003cbr /\u003e\n    \u003ca href=\"#install\"\u003eInstall\u003c/a\u003e\n    ·\n    \u003ca href=\"#reference\"\u003eReference\u003c/a\u003e\n    ·\n    \u003ca href=\"https://discord.com/invite/YNbScHBHrh\"\u003eDiscord\u003c/a\u003e\n  \u003c/p\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/cablehead/http-nu/actions/workflows/ci.yml\"\u003e\n    \u003cimg src=\"https://github.com/cablehead/http-nu/actions/workflows/ci.yml/badge.svg\" alt=\"CI\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://discord.com/invite/YNbScHBHrh\"\u003e\n    \u003cimg src=\"https://img.shields.io/discord/1182364431435436042?logo=discord\" alt=\"Discord\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://crates.io/crates/http-nu\"\u003e\n    \u003cimg src=\"https://img.shields.io/crates/v/http-nu.svg\" alt=\"Crates\"\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\n---\n\n\u003c!-- BEGIN mktoc --\u003e\n\n- [Install](#install)\n  - [eget](#eget)\n  - [Homebrew](#homebrew-macos)\n  - [cargo](#cargo)\n  - [Nix](#nix)\n- [Reference](#reference)\n  - [GET: Hello world](#get-hello-world)\n  - [UNIX domain sockets](#unix-domain-sockets)\n  - [Watch Mode](#watch-mode)\n  - [Reading from stdin](#reading-from-stdin)\n  - [POST: echo](#post-echo)\n  - [Request metadata](#request-metadata)\n  - [Response metadata](#response-metadata)\n  - [Content-Type Inference](#content-type-inference)\n  - [TLS \u0026 HTTP/2 Support](#tls-support)\n  - [Logging](#logging)\n  - [Trusted Proxies](#trusted-proxies)\n  - [Serving Static Files](#serving-static-files)\n  - [Streaming responses](#streaming-responses)\n  - [server-sent events](#server-sent-events)\n  - [Embedded Store](#embedded-store)\n  - [Reverse Proxy](#reverse-proxy)\n  - [Templates](#templates)\n    - [`.mj` - Render inline](#mj---render-inline)\n    - [`.mj compile` / `.mj render` - Precompiled templates](#mj-compile--mj-render---precompiled-templates)\n  - [Syntax Highlighting](#syntax-highlighting)\n  - [Markdown](#markdown)\n  - [Streaming Input](#streaming-input)\n  - [Plugins](#plugins)\n  - [Module Paths](#module-paths)\n  - [Embedded Modules](#embedded-modules)\n    - [Routing](#routing)\n    - [HTML DSL](#html-dsl)\n    - [Datastar SDK](#datastar-sdk)\n- [Eval Subcommand](#eval-subcommand)\n- [Building and Releases](#building-and-releases)\n  - [Available Build Targets](#available-build-targets)\n  - [Examples](#examples)\n  - [GitHub Releases](#github-releases)\n- [History](#history)\n\n\u003c!-- END mktoc --\u003e\n\n## Install\n\n### [eget](https://github.com/zyedidia/eget)\n\n```bash\neget cablehead/http-nu\n```\n\n### Homebrew (macOS)\n\n```bash\nbrew install cablehead/tap/http-nu\n```\n\n### cargo\n\n```bash\ncargo install http-nu --locked\n```\n\n### Nix\n\n```bash\nnix-shell -p http-nu\n```\n\nhttp-nu is available in [nixpkgs](https://github.com/NixOS/nixpkgs). For\npackaging and maintenance documentation, see\n[NIXOS_PACKAGING_GUIDE.md](NIXOS_PACKAGING_GUIDE.md).\n\n## Reference\n\n### GET: Hello world\n\n```bash\n$ http-nu :3001 -c '{|req| \"Hello world\"}'\n$ curl -s localhost:3001\nHello world\n```\n\nOr from a file:\n\n```bash\n$ http-nu :3001 ./serve.nu\n```\n\nCheck out the [`examples/basic.nu`](examples/basic.nu) file in the repository\nfor a complete example that implements a mini web server with multiple routes,\nform handling, and streaming responses.\n\n### UNIX domain sockets\n\n```bash\n$ http-nu ./sock -c '{|req| \"Hello world\"}'\n$ curl -s --unix-socket ./sock localhost\nHello world\n```\n\n### Watch Mode\n\nUse `-w` / `--watch` to automatically reload when files change:\n\n```bash\n$ http-nu :3001 -w ./serve.nu\n```\n\nThis watches the script's directory for any changes (including included files)\nand hot-reloads the handler. Useful during development.\n\n### Reading from stdin\n\nPass `-` to read the script from stdin:\n\n```bash\n$ echo '{|req| \"hello\"}' | http-nu :3001 -\n```\n\nWith `-w`, send null-terminated scripts to hot-reload the handler:\n\n```bash\n$ (printf '{|req| \"v1\"}\\0'; sleep 5; printf '{|req| \"v2\"}') | http-nu :3001 - -w\n```\n\nEach `\\0`-terminated script replaces the handler.\n\n### POST: echo\n\n```bash\n$ http-nu :3001 -c '{|req| $in}'\n$ curl -s -d Hai localhost:3001\nHai\n```\n\n### Request metadata\n\nThe Request metadata is passed as an argument to the closure.\n\n```bash\n$ http-nu :3001 -c '{|req| $req}'\n$ curl -s 'localhost:3001/segment?foo=bar\u0026abc=123' # or\n$ http get 'http://localhost:3001/segment?foo=bar\u0026abc=123'\n─────────────┬───────────────────────────────\n proto       │ HTTP/1.1\n method      │ GET\n uri         │ /segment?foo=bar\u0026abc=123\n path        │ /segment\n remote_ip   │ 127.0.0.1\n remote_port │ 52007\n trusted_ip  │ 127.0.0.1\n             │ ────────────┬────────────────\n headers     │  host       │ localhost:3001\n             │  user-agent │ curl/8.7.1\n             │  accept     │ */*\n             │ ────────────┴────────────────\n             │ ─────┬─────\n query       │  abc │ 123\n             │  foo │ bar\n             │ ─────┴─────\n─────────────┴───────────────────────────────\n\n$ http-nu :3001 -c '{|req| $\"hello: ($req.path)\"}'\n$ http get 'http://localhost:3001/yello'\nhello: /yello\n```\n\n### Response metadata\n\nYou can set the Response metadata using the `.response` custom command.\n\n```nushell\n.response {\n  status: \u003cnumber\u003e  # Optional, HTTP status code (default: 200)\n  headers: {        # Optional, HTTP headers\n    \u003ckey\u003e: \u003cvalue\u003e  # Single value: \"text/plain\"\n    \u003ckey\u003e: [\u003cvalue\u003e, \u003cvalue\u003e]  # Multiple values: [\"cookie1=a\", \"cookie2=b\"]\n  }\n}\n```\n\nHeader values can be strings or lists of strings. Multiple values (e.g.,\nSet-Cookie) are sent as separate HTTP headers per RFC 6265.\n\n```\n$ http-nu :3001 -c '{|req| .response {status: 404}; \"sorry, eh\"}'\n$ curl -si localhost:3001\nHTTP/1.1 404 Not Found\ntransfer-encoding: chunked\ndate: Fri, 31 Jan 2025 08:20:28 GMT\n\nsorry, eh\n```\n\nMulti-value headers:\n\n```nushell\n.response {\n  headers: {\n    \"Set-Cookie\": [\"session=abc; Path=/\", \"token=xyz; Secure\"]\n  }\n}\n```\n\n### Content-Type Inference\n\nContent-type is determined in the following order of precedence:\n\n1. Headers set via `.response` command:\n   ```nushell\n   .response {\n     headers: {\n       \"Content-Type\": \"text/plain\"\n     }\n   }\n   ```\n\n2. Pipeline metadata content-type (e.g., from `to yaml`)\n3. For Record values with no content-type, defaults to `application/json`\n4. Otherwise defaults to `text/html; charset=utf-8`\n\nExamples:\n\n```nushell\n# 1. Explicit header takes precedence\n{|req| .response {headers: {\"Content-Type\": \"text/plain\"}}; {foo: \"bar\"} }  # Returns as text/plain\n\n# 2. Pipeline metadata\n{|req| ls | to yaml }  # Returns as application/x-yaml\n\n# 3. Record auto-converts to JSON\n{|req| {foo: \"bar\"} }  # Returns as application/json\n\n# 4. Default\n{|req| \"Hello\" }  # Returns as text/html; charset=utf-8\n```\n\n### TLS Support\n\nEnable TLS by providing a PEM file containing both certificate and private key:\n\n```bash\n$ http-nu :3001 --tls cert.pem -c '{|req| \"Secure Hello\"}'\n$ curl -k https://localhost:3001\nSecure Hello\n```\n\nGenerate a self-signed certificate for testing:\n\n```bash\n$ openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes\n$ cat cert.pem key.pem \u003e combined.pem\n```\n\nHTTP/2 is automatically enabled for TLS connections:\n\n```bash\n$ curl -k --http2 -si https://localhost:3001 | head -1\nHTTP/2 200\n```\n\n### Logging\n\nControl log output with `--log-format`:\n\n- `human` (default): Live-updating terminal output with startup banner,\n  per-request progress lines showing timestamp, IP, method, path, status,\n  timing, and bytes\n- `jsonl`: Structured JSON lines with `scru128` stamps for log aggregation\n\nEach request emits 3 phases: **request** (received), **response** (headers\nsent), **complete** (body finished).\n\n**Human format**\n\n\u003cimg width=\"1835\" alt=\"human format logging output\" src=\"https://github.com/user-attachments/assets/af4f3022-f362-4c93-82c0-5d18ffb3d9ac\" /\u003e\n\n**JSONL format**\n\nEvents share a `request_id` for correlation:\n\n```bash\n$ http-nu --log-format jsonl :3001 '{|req| \"hello\"}'\n{\"stamp\":\"...\",\"message\":\"started\",\"address\":\"http://127.0.0.1:3001\",\"startup_ms\":42}\n{\"stamp\":\"...\",\"message\":\"request\",\"request_id\":\"...\",\"method\":\"GET\",\"path\":\"/\",\"request\":{...}}\n{\"stamp\":\"...\",\"message\":\"response\",\"request_id\":\"...\",\"status\":200,\"headers\":{...},\"latency_ms\":1}\n{\"stamp\":\"...\",\"message\":\"complete\",\"request_id\":\"...\",\"bytes\":5,\"duration_ms\":2}\n```\n\nLifecycle events: `started`, `reloaded`, `stopping`, `stopped`, `stop_timed_out`\n\n### Trusted Proxies\n\nWhen behind a reverse proxy, use `--trust-proxy` to extract client IP from\n`X-Forwarded-For`. Accepts CIDR notation, repeatable:\n\n```bash\n$ http-nu --trust-proxy 10.0.0.0/8 --trust-proxy 192.168.0.0/16 :3001 '{|req| $req.trusted_ip}'\n```\n\nThe `trusted_ip` field is resolved by parsing `X-Forwarded-For` right-to-left,\nstopping at the first IP not in a trusted range. Falls back to `remote_ip` when:\n\n- No `--trust-proxy` flags provided\n- Remote IP is not in trusted ranges\n- No `X-Forwarded-For` header present\n\n### Serving Static Files\n\nYou can serve static files from a directory using the `.static` command. This\ncommand takes two arguments: the root directory path and the request path.\n\nWhen you call `.static`, it sets the response to serve the specified file, and\nany subsequent output in the closure will be ignored. The content type is\nautomatically inferred based on the file extension (e.g., `text/css` for `.css`\nfiles).\n\nHere's an example:\n\n```bash\n$ http-nu :3001 -c '{|req| .static \"/path/to/static/dir\" $req.path}'\n```\n\nFor single page applications you can provide a fallback file:\n\n```bash\n$ http-nu :3001 -c '{|req| .static \"/path/to/static/dir\" $req.path --fallback \"index.html\"}'\n```\n\n### Streaming responses\n\nValues returned by streaming pipelines (like `generate`) are sent to the client\nimmediately as HTTP chunks. This allows real-time data transmission without\nwaiting for the entire response to be ready.\n\n```bash\n$ http-nu :3001 -c '{|req|\n  .response {status: 200}\n  generate {|_|\n    sleep 1sec\n    {out: (date now | to text | $in + \"\\n\") next: true }\n  } true\n}'\n$ curl -s localhost:3001\nFri, 31 Jan 2025 03:47:59 -0500 (now)\nFri, 31 Jan 2025 03:48:00 -0500 (now)\nFri, 31 Jan 2025 03:48:01 -0500 (now)\nFri, 31 Jan 2025 03:48:02 -0500 (now)\nFri, 31 Jan 2025 03:48:03 -0500 (now)\n...\n```\n\n### [server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events)\n\nUse the `to sse` command to format records for the `text/event-stream` protocol.\nEach input record may contain the optional fields `data`, `id`, `event`, and\n`retry` which will be emitted in the resulting stream.\n\n#### `to sse`\n\nConverts `{data? id? event? retry?}` records into SSE format. Non-string `data`\nvalues are serialized to JSON.\n\nAuto-sets response headers: `content-type: text/event-stream`,\n`cache-control: no-cache`, `connection: keep-alive`.\n\n| input  | output |\n| ------ | ------ |\n| record | string |\n\nExamples\n\n```bash\n\u003e {data: 'hello'} | to sse\ndata: hello\n\n\u003e {id: 1 event: greet data: 'hi'} | to sse\nid: 1\nevent: greet\ndata: hi\n\n\u003e {data: \"foo\\nbar\"} | to sse\ndata: foo\ndata: bar\n\n\u003e {data: [1 2 3]} | to sse\ndata: [1,2,3]\n```\n\n```bash\n$ http-nu :3001 -c '{|req|\n  .response {headers: {\"content-type\": \"text/event-stream\"}}\n  tail -F source.json | lines | from json | to sse\n}'\n\n# simulate generating events in a seperate process\n$ loop {\n  {date: (date now)} | to json -r | $in + \"\\n\" | save -a source.json\n  sleep 1sec\n}\n\n$ curl -si localhost:3001/\nHTTP/1.1 200 OK\ncontent-type: text/event-stream\ntransfer-encoding: chunked\ndate: Fri, 31 Jan 2025 09:01:20 GMT\n\ndata: {\"date\":\"2025-01-31 04:01:23.371514 -05:00\"}\n\ndata: {\"date\":\"2025-01-31 04:01:24.376864 -05:00\"}\n\ndata: {\"date\":\"2025-01-31 04:01:25.382756 -05:00\"}\n\ndata: {\"date\":\"2025-01-31 04:01:26.385418 -05:00\"}\n\ndata: {\"date\":\"2025-01-31 04:01:27.387723 -05:00\"}\n\ndata: {\"date\":\"2025-01-31 04:01:28.390407 -05:00\"}\n...\n```\n\n### Embedded Store\n\nEmbed [cross.stream](https://www.cross.stream) for real-time state and event\nstreaming. Append-only frames, automatic indexing, content-addressed storage.\nEnable with `--store \u003cpath\u003e`:\n\n```bash\n$ http-nu :3001 --store ./store ./serve.nu\n```\n\n**Commands available in handlers:**\n\n| Command   | Description                                      |\n| --------- | ------------------------------------------------ |\n| `.cat`    | Read frames (`-f` follow, `-t` tail, `-T` topic) |\n| `.head`   | Get latest frame for topic (`--follow` stream)   |\n| `.append` | Write frame to topic (`--meta` for metadata)     |\n| `.get`    | Retrieve frame by ID                             |\n| `.remove` | Remove frame by ID                               |\n| `.cas`    | Content-addressable storage operations           |\n| `.id`     | Generate/unpack/pack SCRU128 IDs                 |\n\n**SSE with store:**\n\n```nushell\n{|req|\n  .head quotes --follow\n  | each {|frame| $frame.meta | to dstar-patch-element }\n  | to sse\n}\n```\n\nSee the [xs documentation](https://www.cross.stream) to learn more.\n\n### Reverse Proxy\n\nYou can proxy HTTP requests to backend servers using the `.reverse-proxy`\ncommand. This command takes a target URL and an optional configuration record.\n\nWhen you call `.reverse-proxy`, it forwards the incoming request to the\nspecified backend server and returns the response. Any subsequent output in the\nclosure will be ignored.\n\n**What gets forwarded:**\n\n- HTTP method (GET, POST, PUT, etc.)\n- Request path and query parameters\n- All request headers (with Host header handling based on `preserve_host`)\n- Request body (whatever you pipe into the command)\n\n**Host header behavior:**\n\n- By default: Preserves the original client's Host header\n  (`preserve_host: true`)\n- With `preserve_host: false`: Sets Host header to match the target backend\n  hostname\n\n#### Basic Usage\n\n```bash\n# Simple proxy to backend server\n$ http-nu :3001 -c '{|req| .reverse-proxy \"http://localhost:8080\"}'\n```\n\n#### Configuration Options\n\nThe optional second parameter allows you to customize the proxy behavior:\n\n```nushell\n.reverse-proxy \u003ctarget_url\u003e {\n  headers?: {\u003ckey\u003e: \u003cvalue\u003e}     # Additional headers to add\n  preserve_host?: bool           # Keep original Host header (default: true)\n  strip_prefix?: string          # Remove path prefix before forwarding\n  query?: {\u003ckey\u003e: \u003cvalue\u003e}       # Replace query parameters (Nu record)\n}\n```\n\n#### Examples\n\n**Add custom headers:**\n\n```bash\n$ http-nu :3001 -c '{|req|\n  .reverse-proxy \"http://api.example.com\" {\n    headers: {\n      \"X-API-Key\": \"secret123\"\n      \"X-Forwarded-Proto\": \"https\"\n    }\n  }\n}'\n```\n\n**API gateway with path stripping:**\n\n```bash\n$ http-nu :3001 -c '{|req|\n  .reverse-proxy \"http://localhost:8080\" {\n    strip_prefix: \"/api/v1\"\n  }\n}'\n# Request to /api/v1/users becomes /users at the backend\n```\n\n**Forward original request body:**\n\n```bash\n$ http-nu :3001 -c '{|req| .reverse-proxy \"http://backend:8080\"}'\n# If .reverse-proxy is first in closure, original body is forwarded (implicit $in)\n```\n\n**Override request body:**\n\n```bash\n$ http-nu :3001 -c '{|req| \"custom body\" | .reverse-proxy \"http://backend:8080\"}'\n# Whatever you pipe into .reverse-proxy becomes the request body\n```\n\n**Modify query parameters:**\n\n```bash\n$ http-nu :3001 -c '{|req|\n  .reverse-proxy \"http://backend:8080\" {\n    query: ($req.query | upsert \"context-id\" \"smidgeons\" | reject \"debug\")\n  }\n}'\n# Force context-id=smidgeons, remove debug param, preserve others\n```\n\n### Templates\n\nRender [minijinja](https://github.com/mitsuhiko/minijinja) (Jinja2-compatible)\ntemplates. Pipe a record as context.\n\n#### `.mj` - Render inline\n\n```bash\n$ http-nu :3001 -c '{|req| {name: \"world\"} | .mj --inline \"Hello {{ name }}!\"}'\n$ curl -s localhost:3001\nHello world!\n```\n\nFrom a file:\n\n```bash\n$ http-nu :3001 -c '{|req| $req.query | .mj \"templates/page.html\"}'\n```\n\n#### `.mj compile` / `.mj render` - Precompiled templates\n\nCompile once, render many. Syntax errors caught at compile time.\n\n```nushell\nlet tpl = (.mj compile --inline \"{{ name }} is {{ age }}\")\n\n# Or from file\nlet tpl = (.mj compile \"templates/user.html\")\n\n# Render with data\n{name: \"Alice\", age: 30} | .mj render $tpl\n```\n\nUseful for repeated rendering:\n\n```nushell\nlet tpl = (.mj compile --inline \"{% for i in items %}{{ i }}{% endfor %}\")\n[{items: [1,2,3]}, {items: [4,5,6]}] | each { .mj render $tpl }\n```\n\nCompile once at handler load, render per-request:\n\n```nushell\nlet page = .mj compile \"templates/page.html\"\n\n{|req| $req.query | .mj render $page}\n```\n\nWith HTML DSL (accepts `{__html}` records directly):\n\n```nushell\nuse http-nu/html *\nlet tpl = .mj compile --inline (UL (_for {item: items} (LI (_var \"item\"))))\n{items: [a b c]} | .mj render $tpl\n# \u003cul\u003e\u003cli\u003ea\u003c/li\u003e\u003cli\u003eb\u003c/li\u003e\u003cli\u003ec\u003c/li\u003e\u003c/ul\u003e\n```\n\n### Syntax Highlighting\n\nHighlight code to HTML with CSS classes.\n\n```bash\n$ http-nu eval -c 'use http-nu/html *; PRE { \"fn main() {}\" | .highlight rust } | get __html'\n\u003cpre\u003e\u003cspan class=\"source rust\"\u003e...\n\n$ .highlight lang           # list languages\n$ .highlight theme          # list themes\n$ .highlight theme Dracula  # get CSS\n```\n\n### Markdown\n\nConvert Markdown to HTML with syntax-highlighted code blocks.\n\n```bash\n$ http-nu eval -c '\"# Hello **world**\" | .md | get __html'\n\u003ch1\u003eHello \u003cstrong\u003eworld\u003c/strong\u003e\u003c/h1\u003e\n```\n\nCode blocks use `.highlight` internally:\n\n````bash\n$ http-nu eval -c '\"```rust\nfn main() {}\n```\" | .md | get __html'\n\u003cpre\u003e\u003ccode class=\"language-rust\"\u003e\u003cspan class=\"source rust\"\u003e...\n````\n\n### Streaming Input\n\nIn Nushell, input only streams when received implicitly. Referencing `$in`\ncollects the entire input into memory.\n\n```nushell\n# Streams: command receives input implicitly\n{|req| from json }\n\n# Buffers: $in collects before piping\n{|req| $in | from json }\n```\n\nFor routing, `dispatch` must be first in the closure to receive the body. In\nhandlers, put body-consuming commands first:\n\n```nushell\n{|req|\n  dispatch $req [\n    (route {method: \"POST\"} {|req ctx|\n      from json  # receives body implicitly\n    })\n  ]\n}\n```\n\n### Plugins\n\nLoad Nushell plugins to extend available commands.\n\n```bash\n$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc :3001 '{|req| 5 | inc}'\n$ curl -s localhost:3001\n6\n```\n\nMultiple plugins:\n\n```bash\n$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc --plugin ~/.cargo/bin/nu_plugin_query :3001 '{|req| ...}'\n```\n\nWorks with eval:\n\n```bash\n$ http-nu --plugin ~/.cargo/bin/nu_plugin_inc eval -c '1 | inc'\n2\n```\n\n### Module Paths\n\nMake module paths available with `-I` / `--include-path`:\n\n```bash\n$ http-nu -I ./lib -I ./vendor :3001 '{|req| use mymod.nu; ...}'\n```\n\n### Embedded Modules\n\n#### Routing\n\nhttp-nu includes an embedded routing module for declarative request handling.\nThe request body is available to handlers as `$in`.\n\n```nushell\nuse http-nu/router *\n\n{|req|\n  dispatch $req [\n    # Exact path match\n    (route {path: \"/health\"} {|req ctx| \"OK\"})\n\n    # Method + path\n    (route {method: \"POST\", path: \"/users\"} {|req ctx|\n      .response {status: 201}\n      \"Created\"\n    })\n\n    # Path parameters\n    (route {path-matches: \"/users/:id\"} {|req ctx|\n      $\"User: ($ctx.id)\"\n    })\n\n    # Header matching\n    (route {has-header: {accept: \"application/json\"}} {|req ctx|\n      {status: \"ok\"}\n    })\n\n    # Fallback (always matches)\n    (route true {|req ctx|\n      .response {status: 404}\n      \"Not Found\"\n    })\n  ]\n}\n```\n\nRoutes match in order. First match wins. Closure tests return a record (match,\ncontext passed to handler) or null (no match). If no routes match, returns\n`501 Not Implemented`.\n\n#### HTML DSL\n\nBuild HTML with Nushell. Lisp-style nesting with uppercase tags.\n\n```nushell\nuse http-nu/html *\n\n{|req|\n  (HTML\n    (HEAD (TITLE \"Demo\"))\n    (BODY\n      (H1 \"Hello\")\n      (P {class: \"intro\"} \"Built with Nushell\")\n      (UL { 1..3 | each {|n| LI $\"Item ($n)\" } })\n    )\n  )\n}\n```\n\n`HTML` automatically prepends\n[`\u003c!DOCTYPE html\u003e`](https://html.spec.whatwg.org/multipage/syntax.html#the-doctype).\nAll HTML5 elements available as uppercase commands (`DIV`, `SPAN`, `UL`, etc.).\nAttributes via record, children via args or closure. Lists from `each` are\nautomatically joined. Plain strings are auto-escaped for XSS protection;\n`{__html: \"\u003cb\u003etrusted\u003c/b\u003e\"}` bypasses escaping for pre-sanitized content.\n\n`style` accepts a record; values can be lists for comma-separated CSS (e.g.\n`font-family`): `{style: {font-family: [Arial sans-serif] padding: 10px}}`\n\n`class` accepts a list: `{class: [card active]}`\n\n[Boolean attributes](https://developer.mozilla.org/en-US/docs/Glossary/Boolean/HTML):\n`true` renders the attribute, `false` omits it:\n\n```nushell\nINPUT {type: \"checkbox\" checked: true disabled: false}\n# \u003cinput type=\"checkbox\" checked\u003e\n```\n\n**Jinja2 Template DSL**\n\nFor hot paths, `_var`, `_for`, and `_if` generate Jinja2 syntax that can be\ncompiled once and rendered repeatedly (~200x faster than the runtime DSL):\n\n```nushell\n_var \"user.name\"                              # {{ user.name }}\n_for {item: items} (LI (_var \"item\"))         # {% for item in items %}...{% endfor %}\n_if \"show\" (DIV \"content\")                    # {% if show %}...{% endif %}\n```\n\n```nushell\nlet tpl = .mj compile --inline (UL (_for {item: items} (LI (_var \"item\"))))\n{items: [a b c]} | .mj render $tpl\n# \u003cul\u003e\u003cli\u003ea\u003c/li\u003e\u003cli\u003eb\u003c/li\u003e\u003cli\u003ec\u003c/li\u003e\u003c/ul\u003e\n```\n\n#### Datastar SDK\n\nGenerate [Datastar](https://data-star.dev) SSE events for hypermedia\ninteractions. Follows the\n[SDK ADR](https://github.com/starfederation/datastar/blob/develop/sdk/ADR.md).\n\nCommands return records that pipe to `to sse` for streaming output.\n\n```nushell\nuse http-nu/datastar *\nuse http-nu/html *\n\n{|req|\n  # Parse signals from request (GET query param or POST body)\n  let signals = from datastar-request $req\n\n  [\n    # Update DOM\n    (DIV {id: \"notifications\" class: \"alert\"} \"Profile updated!\"\n    | to dstar-patch-element)\n\n    # Or target by selector\n    (DIV {class: \"alert\"} \"Profile updated!\"\n    | to dstar-patch-element --selector \"#notifications\")\n\n    # Update signals\n    ({count: ($signals.count + 1)} | to dstar-patch-signal)\n\n    # Execute script\n    (\"console.log('updated')\" | to dstar-execute-script)\n  ]\n  | to sse\n}\n```\n\n**Commands:**\n\n```nushell\nto dstar-patch-element [\n  --selector: string           # CSS selector (omit if element has ID)\n  --mode: string               # outer, inner, replace, prepend, append, before, after, remove (default: outer)\n  --namespace: string          # Content namespace: html (default) or svg\n  --use_view_transition        # Enable CSS View Transitions API\n  --id: string                 # SSE event ID for replay\n  --retry: int                 # Reconnection delay in ms\n]: string -\u003e record\n\nto dstar-patch-signal [\n  --only_if_missing            # Only set signals not present on client\n  --id: string\n  --retry: int\n]: record -\u003e record\n\nto dstar-execute-script [\n  --auto_remove: bool          # Remove \u003cscript\u003e after execution (default: true)\n  --attributes: record         # HTML attributes for \u003cscript\u003e tag\n  --id: string\n  --retry: int\n]: string -\u003e record\n\nfrom datastar-request [req: record]: string -\u003e record  # $in | from datastar-request $req\n```\n\n## Eval Subcommand\n\nTest http-nu commands without running a server.\n\n```bash\n# From command line\n$ http-nu eval -c '1 + 2'\n3\n\n# From file\n$ http-nu eval script.nu\n\n# From stdin\n$ echo '1 + 2' | http-nu eval -\n3\n\n# Test .mj commands\n$ http-nu eval -c '.mj compile --inline \"Hello, {{ name }}\" | describe'\nCompiledTemplate\n```\n\n## Building and Releases\n\nThis project uses [Dagger](https://dagger.io) for cross-platform containerized\nbuilds that run identically locally and in CI. This means you can test builds on\nyour machine before pushing tags to trigger releases.\n\n### Available Build Targets\n\n- **Windows** (`windows-build`)\n- **macOS ARM64** (`darwin-build`)\n- **Linux ARM64** (`linux-arm-64-build`)\n- **Linux AMD64** (`linux-amd-64-build`)\n\n### Examples\n\nBuild a Windows binary locally:\n\n```bash\ndagger call windows-build --src upload --src \".\" export --path ./dist/\n```\n\nGet a throwaway terminal inside the Windows builder for debugging:\n\n```bash\ndagger call windows-env --src upload --src \".\" terminal\n```\n\n**Note:** Requires Docker and the [Dagger CLI](https://docs.dagger.io/install).\nThe `upload` function filters files to avoid uploading everything in your local\ndirectory.\n\n### GitHub Releases\n\nThe GitHub workflow automatically builds all platforms and creates releases when\nyou push a version tag (e.g., `v1.0.0`). Development tags containing `-dev.` are\nmarked as prereleases.\n\n## History\n\nIf you prefer POSIX to [Nushell](https://www.nushell.sh), this project has a\ncousin called [http-sh](https://github.com/cablehead/http-sh).\n","funding_links":[],"categories":["Integrations"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcablehead%2Fhttp-nu","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcablehead%2Fhttp-nu","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcablehead%2Fhttp-nu/lists"}