{"id":15375813,"url":"https://github.com/justinfagnani/zipadee","last_synced_at":"2025-04-13T18:14:12.662Z","repository":{"id":250075673,"uuid":"833398925","full_name":"justinfagnani/zipadee","owner":"justinfagnani","description":"A simple Node HTTP server with middleware","archived":false,"fork":false,"pushed_at":"2024-10-18T17:15:32.000Z","size":329,"stargazers_count":25,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-03T10:40:48.321Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","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/justinfagnani.png","metadata":{"files":{"readme":"README.md","changelog":null,"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":"2024-07-25T01:18:26.000Z","updated_at":"2025-01-30T09:05:13.000Z","dependencies_parsed_at":"2024-07-29T22:37:04.475Z","dependency_job_id":"d7bb4dfe-06c9-408f-8b6b-232829c21c21","html_url":"https://github.com/justinfagnani/zipadee","commit_stats":{"total_commits":31,"total_committers":1,"mean_commits":31.0,"dds":0.0,"last_synced_commit":"eb38d1232c8a93600ed91a82fa9a884709b27344"},"previous_names":["justinfagnani/zipadee"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinfagnani%2Fzipadee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinfagnani%2Fzipadee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinfagnani%2Fzipadee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/justinfagnani%2Fzipadee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/justinfagnani","download_url":"https://codeload.github.com/justinfagnani/zipadee/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248758418,"owners_count":21156957,"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":[],"created_at":"2024-10-01T14:04:59.134Z","updated_at":"2025-04-13T18:14:12.638Z","avatar_url":"https://github.com/justinfagnani.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Zipadee\n\nZipadee is a simple Node HTTP server with middleware.\n\n\u003e [!CAUTION]\n\u003e Zipadee is very early, under construction, will change a lot, and may never be sufficiently maintained for any level of use. If you want to try it, please consider contributing!\n\n\u003e [!IMPORTANT]\n\u003e I'm looking for collaborators for Zipadee! I don't have enough time to make\n\u003e Zipadee a well-supported project on my own, and I can't possibly use it in\n\u003e enough different cases to shake out all of the basic features and initial\n\u003e bugs. It the goals and principles of the project speak to you, reach out!\n\nZipadee is inspired by Koa and Express, with the following goals:\n\n- Ergonomic: Usability improvements on raw Node HTTP APIs\n- Familiar: App and Middleware similar to Express and Koa\n- Simple: A small API over Node HTTP\n- Lightweight: Minimal features and dependencies\n- Great TypeScript support:\n  - Written in TypeScript so typings are always included and accurate\n  - Objects have fixed shapes that are easy to type\n- Safe: The easist way to repond with HTML is escaped by default to protect\n  against XSS\n- Convenient: The most common needs are easy to address, whether with a built-in\n  feature or first-class middleware.\n\n```ts\nimport {App, html} from 'zipadee';\n\nconst users = ['Alice', 'Bob'];\n\nconst app = new App();\n\napp.use(async (req, res) =\u003e {\n  res.body = html`\n    \u003ch1\u003eHello world!\u003c/h1\u003e\n    \u003ch2\u003eUsers:\u003c/h2\u003e\n    \u003cul\u003e\n      ${users.map((user, i) =\u003e html` \u003cli\u003eUser ${i}: ${user}\u003c/li\u003e `)}\n    \u003c/ul\u003e\n  `;\n});\n\napp.listen(8080);\n```\n\n## Principles and features\n\n### Fixed object shapes\n\nOne of the biggest differences from Koa, and other web server frameworks as\nwell, is that Zipadee doesn't encourage middleware to change the shape of\nobjects as a standard way of passing data between middleware or plugins -\nwhether adding new properties to objects or changing the types of properties.\n\nChanging object shapes are hard for everything that deals with code to\nhandle well: VMs, compilers and type-checkers, and humans.\n\nFor instance, in other server frameworks, middleware will often add new\nproperties to a context object. Later middleware will access those properties,\nand only work if the other middleware is present, but there's very little to\ntell code readers about that dependency.\n\nZipadee doesn't have a context object, but instead encourages middleware that\nneeds to vend data to other middleware to do so via lookup APIs.\n\nZipadee also doesn't include a way to replace the Request and Response classes,\nso middleware always knows the types of objects that it is receiving.\n\n#### Examples of alternatives to context\n\n##### Get data from Request and Response objects with helpers\n\nConsider a CSP middleware that helps set the Content-Security-Policy\nheader and generate nonces to use in HTML generated by downstream middleware.\nInstead of adding a `nonce` property to a context, the middleware can offer a\nutility function to get the nonce from the Response object. This function can\nread from a WeakMap keyed by the Response (it could add a property to Response\ntoo, but Zipadee discourages that).\n\n```ts\nimport {csp, getNonce} from 'some-zipadee-csp-package';\n\napp.use(csp({styleNonce: true}));\n\napp.use(async (req, res) =\u003e {\n  // The getNonce() function can give nice error messages if the csp()\n  // middleware wasn't used, and it can have nice hover-over docs for developers\n  const styleNonce = getNonce(res);\n  res.body = html`\u003cstyle nonce=${styleNonce}`\u003e...\u003c/style\u003e`;\n});\n```\n\n##### Use functions instead of middleware\n\nRequest body parsing is commonly done with middleware, but to pass the parsed\nrequest body to downstream middleware, parsers usually modify the Request\nobject. This reads the body stream before other middleware can, and changes the\ntype of the body (say to JSON). Given that these changes cna cause downstream\nbugs, Zipadee encourages using simple functions instead. Middleware that needs\nto parse the request body as JSON can just use a function:\n\n```ts\nimport {parseBody} from 'some-body-parser';\n\napp.use(async (req, res, next) =\u003e {\n  const json = await parseBody(req);\n  //     ^ this is typed nicely as a JSON object. No guessing\n});\n```\n\nIf multiple readers need access to the parsed body, `parseBody()` could cache\nthe result in a WeakMap.\n\n### Safety\n\nZipadee aims to have safe default behavior. Part of this is treating all text\nresponses as plain text by default, and only responding with a MIME type of\n`text/html` (or other) if the developer specifically sets the type, or uses the\nbuilt-in `html` template tag.\n\n```ts\nimport {App, html} from 'zipadee';\n\napp.use((req, res) =\u003e {\n  // This is treated as plain text\n  res.body = '\u003ch1\u003eHello World!\u003c/h1\u003e';\n});\n\napp.use((req, res) =\u003e {\n  // This is treated as HTML\n  res.body = html`\u003ch1\u003eHello World!\u003c/h1\u003e`;\n});\n```\n\nIn Koa, when a response body is a string it is scanned for what looks like part\nof an HTML end tag (\"`\u003c/`\") and if it's found, Koa automatically sets the MIME\ntype of the response to `text/html`. This too easily allows mixing of trusted\nnad untrusted content, which can lead to XSS vulnerabilities.\n\nZipadee's `html` template tag automatically escapes untrusted interpolations.\nDevelopers must use the `unsafeHTML()` utility to interpolate\nnon-template-literal strings, encouraging developers to think about safety\nup-front.\n\n### Built-in HTML templating\n\nZipadee's `html` template tag offers a number of conveniences that may eliminate\nthe need for a separate HTML template system like Liquid or Nunjucks:\n\n- Automatic escaping\\* of untrusted interpolated strings\n- Composition of nested templates\n- Support for arrays: Makes it easy to build lists without having to use\n  `arr.join('')`, and supports streaming.\n- Pretty-ish-printing: Templates can be automatically dedented and nested\n  templates re-indented when a nicer looking output is desired.\n- Asynchronous templates with streaming support: Zipadee streams each chunk of a\n  template as its ready, and automatically waits for Primises in the stream.\n\n  ```ts\n  app.use(async (req, res) =\u003e {\n    // Render the shell ASAP, render the body when data is loaded\n    res.body = html`\n      \u003chtml\u003e\n        \u003chead\u003e\n          \u003cscript type=\"module\" src=\"./app.js\"\u003e\u003c/script\u003e\n          \u003clink rel=\"stylesheet href=\"./app.css\"\u003e\n        \u003c/head\u003e\n        \u003cbody\u003e\n          ${renderBody(req)}\n        \u003c/body\u003e\n      \u003c/html\u003e\n    `;\n  });\n\n  const renderBody = async (req) =\u003e {\n    const data = await getData(req);\n    return html` \u003ch1\u003e${data.title}\u003c/h1\u003e `;\n  };\n  ```\n\n\\*Zipadee's `html` tag does not yet perform contextual auto-escaping, which\nrequires parsing the HTML templates, so it's still possible to create unsafe\nattribute values like `javascript:...`.\n\n### Core utilities included\n\nUtilities like `compose()` and `mount()` are core to using middleware correctly,\nand help define some of the semantics of middleware, so they are built-in to\nZipadee.\n\n`compose()` specifically is use by the `App` class to compose the top-level\nmiddleware. Including it is one less dependency in core.\n\n`mount()` defines in part how middleware should handle paths. It doesn't modify\na Request's URL object, but the Request's `path`, so all middleware should use\nthe `path` property to make sure that it can be mounted properly. Because of how\nimportant that is, `mount()` is built-in.\n\n## TODO\n\nThere are many, many things to be done if Zipadee is ever going to be real, but here's a few of them:\n\n- [ ] Web site\n- [ ] Finish `@zipadee/static`\n- [ ] Finish `@zipadee/router`\n- [ ] Benchmarks (Use Fastify's? It's maye too simple)\n- [ ] Body parsers (and JSON Schema validation for JSON bodies?)\n- [ ] `@zipadee/csp` package that makes it easy to set CSP headers and to create nonces and use them in other middleware.\n\n  ```ts\n  import {getNonce} from '@zipadee/csp';\n\n  app.use((req, res) =\u003e {\n    res.body = `\u003cscript nonce=${getNonce(res)}\u003e...\u003c/script\u003e`;\n  });\n  ```\n\n- [ ] `@zipadee/etag` (or `@zipadee/cache`?) - easily set ETag header from content. Maybe it could be used by `@zipadee/static`.\n- [ ] http2: test against Node's compatability layer, offer a switch?\n  - Local cert generation for local HTTP/2 dev servers?\n- [ ] `@zipadee/compress` midleware\n- [ ] `@zipadee/trpc` midleware\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustinfagnani%2Fzipadee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjustinfagnani%2Fzipadee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjustinfagnani%2Fzipadee/lists"}