{"id":16022900,"url":"https://github.com/artur-borys/lapis","last_synced_at":"2026-04-30T11:31:57.063Z","repository":{"id":62421651,"uuid":"291339046","full_name":"artur-borys/lapis","owner":"artur-borys","description":"A Deno express-like HTTP server","archived":false,"fork":false,"pushed_at":"2020-09-15T05:24:40.000Z","size":31,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-17T02:18:47.367Z","etag":null,"topics":["api","deno","express","http","javascript","lapis","server"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/artur-borys.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-08-29T19:51:20.000Z","updated_at":"2022-11-20T11:20:23.000Z","dependencies_parsed_at":"2022-11-01T17:32:40.507Z","dependency_job_id":null,"html_url":"https://github.com/artur-borys/lapis","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/artur-borys/lapis","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artur-borys%2Flapis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artur-borys%2Flapis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artur-borys%2Flapis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artur-borys%2Flapis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/artur-borys","download_url":"https://codeload.github.com/artur-borys/lapis/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/artur-borys%2Flapis/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32463891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":["api","deno","express","http","javascript","lapis","server"],"created_at":"2024-10-08T18:42:47.315Z","updated_at":"2026-04-30T11:31:52.045Z","avatar_url":"https://github.com/artur-borys.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Lapis\n\nThis Deno module is created mainly for myself - to learn JS/TS.\nI will try to make it resemble `express` in a way. It's a wrapper around deno's std/http module.\n\nYou can use this module if you wish, but it's not production ready and probably never will be - it's just for my own skills development.\n\n## Features\n\n- middlewares - standard and error handlers. Can be used on server instance and on routers\n- routing - you can create and connect routers. Each router can have a base path\n- path and query params\n- cookies\n- request data parsing\n  - JSON\n  - plain text\n- response encoding\n  - JSON\n  - plain text\n\n## Example\n\n```typescript\nimport { Lapis, Router, MiddlewareFunction } from \"./mod.ts\";\nconst PORT = 3000;\nconst lapis = new Lapis();\nconst apiRouter = new Router(\"/api\");\nconst usersRouter = new Router(\"/users\");\nconst postsRouter = new Router(\"/posts\");\n\n// currently it's best to define standard middleware as a separate named function\n// to get type hints - use() recognizes only Router and ErrorMiddlewareFunction for some reason\n// I need to sort it out - I'm new to TypeScript\nconst requestLogger: MiddlewareFunction = (req, res, next) =\u003e {\n  console.log(\n    `Got a request from ${req.remoteAddr.hostname}: ${req.method} ${req.url}`\n  );\n  next();\n};\n\nlapis.use(requestLogger);\nlapis.use(Lapis.cookies);\n\nlapis.get(\"/\", (req, res) =\u003e {\n  console.log(req.cookies?.toString());\n  if (req.cookies?.has(\"someCookie\")) {\n    res.cookies?.delete(\"someCookie\");\n  } else {\n    res.cookies?.set({ name: \"someCookie\", value: \"someValue\" });\n  }\n\n  res.send(\"Welcome to my API server\");\n});\n\napiRouter.get(\"/error\", (req, res, next) =\u003e {\n  // this will be handled by error handler set on lapis instance\n  next(new Error(\"Oh no! Expected error has occured!\"));\n});\n\napiRouter.get(\"/unhandled\", (req, res, next) =\u003e {\n  // will be handled by Lapis default error handler\n  // it will print error to console and send to user 500 { ok: false }\n  throw new Error(\"Oh no! Unexpected error!\");\n});\n\nusersRouter.get(\"/\", (req, res) =\u003e {\n  if (req.query.pro !== undefined) {\n    // let's assume that pro can be 0 or 1\n    const pro = Boolean(Number(req.query.pro));\n    const foundUsers = users.filter((user) =\u003e user.pro === pro);\n    res.send(foundUsers);\n  } else {\n    res.send(users);\n  }\n});\n\nusersRouter.get(\"/:id\", (req, res, next) =\u003e {\n  const user = users.find(\n    (candidate) =\u003e candidate.id === Number(req.params.id)\n  );\n  if (!user) {\n    // IMPORTANT - next() has to be called on logical leaf of a function\n    return next(new Error(\"NOT_FOUND\"));\n  }\n  // we could remove return above and insert else {} here\n  res.send(user);\n});\n\nusersRouter.get(\"/:id/posts\", (req, res, next) =\u003e {\n  const userPosts = posts.filter(\n    (post) =\u003e post.authorId === Number(req.params.id)\n  );\n  res.send(userPosts);\n});\n\n// it doesn't make sense, but it's just an example\n// also, this route will confuse the previous route /:id/posts\n// but it's only for example purpose\nusersRouter.get(\"/:id/posts/:post_id\", (req, res, next) =\u003e {\n  console.log(req.params); // both id and post_id should show up here\n  const post = posts.find((_post) =\u003e _post.id === Number(req.params.post_id));\n  res.send(post);\n});\n\npostsRouter.get(\"/\", (req, res, next) =\u003e {\n  res.send(posts);\n});\n\npostsRouter.get(\"/:id\", (req, res, next) =\u003e {\n  const post = posts.find((_post) =\u003e _post.id === Number(req.params.id));\n  if (!post) {\n    /*\n      this error doesn't have any handler in postsRouter.\n      usersRouter \"doesn't have jurisdiction\" in here, so it can't handle it\n      lapis instance will handle it (see error middleware at the bottom)\n    */\n    next(new Error(\"POST_NOT_FOUND\"));\n  } else {\n    res.send(post);\n  }\n});\n\n// You can specify more than one middleware!\npostsRouter.post(\n  \"/\",\n  (req, res, next) =\u003e {\n    const valid = req.body.title \u0026\u0026 req.body.content \u0026\u0026 req.body.authorId;\n    if (valid) {\n      next();\n    } else {\n      res.status(400).send({\n        error: \"INVALID_BODY\",\n      });\n    }\n  },\n  (req, res, next) =\u003e {\n    // Request content-type should be application/json\n    const newPost = {\n      id: posts.length + 1,\n      authorId: req.body.authorId,\n      title: req.body.title,\n      content: req.body.content,\n    };\n    posts.push(newPost);\n    res.status(201).send(newPost);\n  }\n);\n\n// this error handler will match only errors that occured in /api/users/*\nusersRouter.use((err, req, res, next) =\u003e {\n  if (err instanceof Error) {\n    if (err.message === \"NOT_FOUND\") {\n      return res.status(404).send({\n        error: err.message,\n      });\n    } else {\n      res.status(400).send({\n        error: \"BAD_REQUEST\",\n      });\n    }\n  } else {\n    res.status(500).send({\n      error: \"INTERNAL_SERVER_ERROR\",\n    });\n  }\n});\n\napiRouter.use(usersRouter);\napiRouter.use(postsRouter);\n\nlapis.use(apiRouter);\n\n/*\n  error handler MUST ALWAYS have 4 parameters -\n  it's neccessary for Lapis to recognize if that middleware is able to handle an error\n*/\nlapis.use((err, req, res, next) =\u003e {\n  res.status(500).send({\n    ok: false,\n    fromServerRoot: true,\n    error: err instanceof Error ? err.message : err,\n  });\n});\n\nlapis.listen({ port: PORT }).then(() =\u003e {\n  console.log(`Listening on ${PORT}`);\n});\n```\n\n## To Do\n\nFeatures I'm planning to implement:\n\n- authentication\n  - base\n  - bearer token (JSON Web Tokens)\n- custom request and response attributes, that will\n  enable storing more metadata (for example req.user after authentication)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartur-borys%2Flapis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fartur-borys%2Flapis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fartur-borys%2Flapis/lists"}