{"id":13528352,"url":"https://github.com/adieuadieu/alagarr","last_synced_at":"2025-04-01T11:31:28.890Z","repository":{"id":31103106,"uuid":"106009846","full_name":"adieuadieu/alagarr","owner":"adieuadieu","description":"🦍 Alagarr is a request-response helper library that removes the boilerplate from your Node.js (AWS Lambda) serverless functions and helps make your code portable.","archived":true,"fork":false,"pushed_at":"2023-03-22T14:00:56.000Z","size":1351,"stargazers_count":59,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-20T13:48:27.718Z","etag":null,"topics":["aws-apigateway","aws-lambda","aws-lambda-node","faas","functionalesque-programming","no-dependencies","no-if-statement","request","response","serverless"],"latest_commit_sha":null,"homepage":"","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/adieuadieu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","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},"funding":{"github":"adieuadieu"}},"created_at":"2017-10-06T13:49:05.000Z","updated_at":"2023-09-08T17:30:51.000Z","dependencies_parsed_at":"2024-06-19T03:04:03.754Z","dependency_job_id":"2bc89c24-0eed-467a-a271-8599d9421069","html_url":"https://github.com/adieuadieu/alagarr","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adieuadieu%2Falagarr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adieuadieu%2Falagarr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adieuadieu%2Falagarr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adieuadieu%2Falagarr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adieuadieu","download_url":"https://codeload.github.com/adieuadieu/alagarr/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246631949,"owners_count":20808788,"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":["aws-apigateway","aws-lambda","aws-lambda-node","faas","functionalesque-programming","no-dependencies","no-if-statement","request","response","serverless"],"created_at":"2024-08-01T06:02:27.594Z","updated_at":"2025-04-01T11:31:28.328Z","avatar_url":"https://github.com/adieuadieu.png","language":"TypeScript","funding_links":["https://github.com/sponsors/adieuadieu"],"categories":["TypeScript"],"sub_categories":[],"readme":"![](docs/title-logo.png)\n\nAlagarr is a request-response helper library for serverless/faas functions\u003csup\u003e\\*\u003c/sup\u003e invoked via HTTP events (e.g. via API Gateway). Alagarr makes your code portable: it abstracts the event-context-callback function signatures of various serverless-providers so that you can spend less time writing boring function-as-a-service-related boilerplate.\n\nAlagarr is a higher-order function which abstracts the the programming models of various serverless-cloud providers and adds a standardized request-response model extensible through composable middleware functions. It's API is concise and will be familiar to anyone who's worked with Express.js. It comes with built-in error handling which makes it trivial to implement error-recovery strategies.\n\n\u003csup\u003e\\*\u003c/sup\u003eCurrently: AWS Lambda/API Gateway. Next: GCP \u0026 Azure\n\n[![CircleCI](https://img.shields.io/circleci/project/github/adieuadieu/alagarr/master.svg?style=flat-square)](https://circleci.com/gh/adieuadieu/alagarr)\n[![Coveralls](https://img.shields.io/coveralls/adieuadieu/alagarr/master.svg?style=flat-square)](https://coveralls.io/github/adieuadieu/alagarr)\n[![David](https://img.shields.io/david/adieuadieu/alagarr.svg?style=flat-square)]()\n[![David](https://img.shields.io/david/dev/adieuadieu/alagarr.svg?style=flat-square)]()\n\nWithout Alagarr:\n\n```js\n// AWS Lambda / API Gateway\nmodule.exports.myHandler = function(event, context, callback) {\n  callback(null, {\n    statusCode: 200,\n    body: JSON.stringify({ foo: 'bar' }),\n    headers: {\n      'content-type': 'application/json',\n    },\n  })\n}\n```\n\nWith Alagarr:\n\n```js\nconst alagarr = require('alagarr')\n\nmodule.exports.myHandler = alagarr(() =\u003e ({ foo: 'bar' }))\n```\n\n## Contents\n\n1.  [Features](#features)\n1.  [Full Example](#full-example)\n1.  [Installation \u0026 Usage](#installation--usage)\n1.  [Configuration](#configuration)\n    1.  [Options](#configuration-options)\n1.  [API](#api)\n1.  [Error Handling](#error-handling)\n1.  [Logging](#logging)\n1.  [Middleware](#middleware)\n    1.  [Request Middleware](#request-middleware)\n    1.  [Response Middleware](#response-middleware)\n    1.  [Custom Middleware](#custom-middleware)\n1.  [Contributing](#contributing)\n1.  [Similar Projects](#similar-projects)\n1.  [Related Thingies](#related-thingies)\n1.  [License](#license)\n\n## Features\n\n* Concise \u0026 familiar API\n* Zero dependencies\n* Fully tested\n* Built-in error handling makes catching and throwing errors a breeze\n* Kibana-ready request logging\n* Middleware for common tasks included\n* Request cookie parsing\n* Normalized request headers\n* Includes request body parsers\n* Response CSP headers\n* Response gzipping/deflate\n* Easily respond with images/binary data\n* Support for custom middleware\n\n## Full Example\n\nAlagarr helps you cut out all the boilerplate involved with handling HTTP requests in serverless functions. Albeit somewhat contrived, here is a before-and-after example of a common pattern frequently found in AWS Lambda function's:\n\n#### Without Alagarr 😭\n\n```javascript\nconst got = require('got')\n\nmodule.exports.handler = function(event, context, callback) {\n  const { queryStringParameters: { currency } } = event\n\n  if (!currency) {\n    callback(null, {\n      statusCode: 400,\n      body: JSON.stringify({\n        message: 'Please provide the \"currency\" query parameter.',\n      }),\n      headers: {\n        'content-type': 'application/json',\n      },\n    })\n  }\n\n  got(`https://api.coinmarketcap.com/v1/ticker/${currency}`)\n    .then(response =\u003e {\n      callback(null, {\n        statusCode: 200,\n        body: JSON.stringify(response),\n        headers: {\n          'content-type': 'application/json',\n        },\n      })\n    })\n    .catch(error =\u003e {\n      callback(null, {\n        statusCode: error.statusCode,\n        body: JSON.stringify({\n          error: error.response,\n        }),\n        headers: {\n          'content-type': 'application/json',\n        },\n      })\n    })\n}\n```\n\n#### With Alagarr 😁\n\n```javascript\nconst { alagarr, ClientError } = require('alagarr') // @TODO: this require is wrong\nconst got = require('got')\n\nmodule.exports.handler = alagarr((request, response) =\u003e {\n  const { query: { currency } } = request\n\n  if (!currency) {\n    throw new ClientError('Please provide the \"currency\" query parameter.')\n  }\n\n  return got(`https://api.coinmarketcap.com/v1/ticker/${currency}`)\n})\n```\n\nThere are a few things being handled for you in the above Alagarr example:\n\n* The programming model has been normalized. You can run this code without modification on any of the [supported](#supported-providers) cloud/faas/serverless providers. Not just AWS Lambda. Alagarr makes your code portable.\n* The `callback()` is being handled for you. Alagarr will set the status code, content-type, and body appropriately. More on this behavior [here](#api-alagarr-handlerFunction).\n* Errors are caught for you and turned into something appropriate for the client based on the type of error. If you don't like the default behavior, you can [provide your own](#error-handling) error handler.\n\n## Installation \u0026 Usage\n\nInstall Alagarr with NPM or Yarn:\n\n```bash\nnpm install alagarr\n```\n\nThen include it in your serverless function:\n\n```js\nconst alagarr = require('alagarr')\n\nmodule.exports.exampleHandler = alagarr(request =\u003e {\n  const { path, provider } = request\n\n  return `You've ended up at ${path} on the ${provider} cloud.`\n})\n```\n\n## Configuration\n\nAlagarr ships with default configuration that should work for most use-cases. But, it's possible to pass a configuration object as the second parameter to the alagar() function:\n\n```js\nconst alagarr = require('alagarr')\n\nmodule.exports.handler = alagarr(() =\u003e 'Hello world!', {\n  headers: {},\n  logger: console.log,\n})\n```\n\n### Configuration Options\n\nThe available configuration options are outlined here:\n\n| Option                 | Default | Description                                                                                                                                                                              |\n| ---------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| **cspPolicies**        | []      | List of CSP policies to include in the response headers                                                                                                                                  |\n| **errorHandler**       |         | Provide a custom error handler. See the section on [Error Handling](#error-handling) for more details                                                                                    |\n| **headers**            | {}      | Headers to include in every response                                                                                                                                                     |\n| **logger**             |         | Logger to use to log requests. If undefined, Alagarr will use an internal logger. Logging can be disabled by setting to `false`. See the section on [Logging](#logging) for more details |\n| **requestMiddleware**  |         | Array of custom request middleware to use. See the section on [Request Middleware](#request-middleware) for more details                                                                 |\n| **responseMiddleware** |         | Array of custom response middleware to use. See the section on [Response Middleware](#request-middleware) for more details                                                               |\n\n## API\n\n### Alagarr module\n\n* [`alagarr()`](#api-alagarr-module-default)\n* [`requestMiddleware`](#api-alagarr-module-request-middleware)\n* [`responseMiddleware`](#api-alagarr-module-response-middleware)\n* [`ClientError()`](#api-alagarr-module-client-error)\n* [`ServerError()`](#api-alagarr-module-server-error)\n\n### Request methods\n\n* [`request.body`](#api-request-body)\n* [`request.context`](#api-request-context)\n* [`request.cookies`](#api-request-cookies)\n* [`request.headers`](#api-request-headers)\n* [`request.hostname`](#api-request-hostname)\n* [`request.meta`](#api-request-meta)\n* [`request.method`](#api-request-method)\n* [`request.path`](#api-request-path)\n* [`request.provider`](#api-request-provider)\n* [`request.query`](#api-request-query)\n* [`request.source`](#api-request-source)\n* [`request.timestamp`](#api-request-timestamp)\n\n### Response methods\n\n* [`response.json()`](#api-response-json)\n* [`response.html()`](#api-response-html)\n* [`response.text()`](#api-response-text)\n* [`response.svg()`](#api-response-svg)\n* [`response.png()`](#api-response-png)\n* [`response.jpeg()`](#api-response-jpeg)\n* [`response.respondTo()`](#api-response-respondTo)\n* [`response.raw()`](#api-response-raw)\n\n---\n\n\u003ca name=\"api-alagarr-module-default\" /\u003e\n\n### alagarr(handlerFunction, configurationOptions?): void\n\n@TODO\n\n```javascript\nconst alagarr = require('alagarr')\n\nconst configurationOptions = {\n  logger: false,\n}\n\nconst handlerFunction = function(request, response) {\n  const { query: { name } } = request\n  return response.html(`Hello ${name}.`)\n}\n\nmodule.exports.handler = alagar(handlerFunction, configurationOptions)\n```\n\n\u003ca name=\"api-alagarr-handlerFunction\" /\u003e\n\nThe `handlerFunction` has a function signature of:\n\n```typescript\nexport type HandlerFunction = (\n  request: any,\n  response: any,\n) =\u003e string | object | void | Promise\u003cstring | object | void\u003e\n```\n\nIf your `handlerFunction` returns falsey, then it's your responsibility to call the appropriate response method to end the invocation (e.g. `response.json()`). For convenience, if the `handlerFunction` returns a string, the result will be passed to `response.html()` or `response.text()` for you. Alternatively, if the handler returns an object, it will be passed to `response.json()`. You may also return a Promise (or make your handler `async`).\n\n---\n\n\u003ca name=\"api-alagarr-module-request-middleware\" /\u003e\n\n### requestMiddleware\n\n@TODO\n\n---\n\n\u003ca name=\"api-alagarr-module-response-middleware\" /\u003e\n\n### responseMiddleware\n\n@TODO\n\n---\n\n\u003ca name=\"api-alagarr-module-client-error\" /\u003e\n\n### ClientError(message, statusCode = 400)\n\n@TODO\n\n---\n\n\u003ca name=\"api-alagarr-module-server-error\" /\u003e\n\n### ServerError(message, statusCode = 500)\n\n@TODO\n\n---\n\n\u003ca name=\"api-request-body\" /\u003e\n\n### request.body\n\nThe request body, if any. If using default request middleware, or another body parser, this value will contain the parsed contents of the request body.\n\n```typescript\nreadonly body: string | object\n```\n\n---\n\n\u003ca name=\"api-request-context\" /\u003e\n\n### request.context\n\nThe provider context object.\n\nOn AWS Lambda this is the second parameter passed to a Lambda function's handler.\n\n```typescript\nreadonly context: object\n```\n\n---\n\n\u003ca name=\"api-request-cookies\" /\u003e\n\n### request.cookies\n\nAn object containing the cookies included with the request.\n\n```typescript\nreadonly cookies: {\n  readonly [name: string]: string\n}\n```\n\n---\n\n\u003ca name=\"api-request-headers\" /\u003e\n\n### request.headers\n\nAn object containing all of the headers included in the request.\n\n```typescript\nreadonly headers: {\n  readonly [name: string]: string\n}\n```\n\n---\n\n\u003ca name=\"api-request-hostname\" /\u003e\n\n### request.hostname\n\nThe request's hostname. Derived from the request's `Host` header.\n\n```typescript\nreadonly hostname: string\n```\n\n---\n\n\u003ca name=\"api-request-meta\" /\u003e\n\n### request.meta\n\nAn object containing some meta data about the invocation. It includes:\n\n```typescript\nreadonly meta: {\n  readonly coldStart: boolean, // was this a cold start?\n  readonly invocationCount: number, // number of times this container has been invoked\n}\n```\n\n---\n\n\u003ca name=\"api-request-method\" /\u003e\n\n### request.method\n\nThe request HTTP method. E.g `GET` or `POST`.\n\n```typescript\nreadonly method: enum {\n  'GET',\n  'POST',\n  'PATCH',\n  'DELETE',\n}\n```\n\n---\n\n\u003ca name=\"api-request-path\" /\u003e\n\n### request.path\n\nThe request path.\n\n```typescript\nreadonly path: string\n```\n\n---\n\n\u003ca name=\"api-request-provider\" /\u003e\n\n### request.provider\n\nThe name of the current request's provider. Possible values include: `aws`\n\n```typescript\nreadonly provider: enum {\n  'aws'\n}\n```\n\n---\n\n\u003ca name=\"api-request-query\" /\u003e\n\n### request.query\n\nAn object of query parameters included in the request.\n\nGiven a request:\n\n```\nGET http://example.com?foo=1\u0026bar=2\n```\n\n`request.query` will contain:\n\n```javascript\n{\n  foo: '1',\n  bar: '2',\n}\n```\n\n```typescript\nreadonly query: {\n  readonly [name: string]: string\n}\n```\n\n---\n\n\u003ca name=\"api-request-source\" /\u003e\n\n### request.source\n\nThe name of the current request's invocation source. Possible values include: `api-gateway`\n\n```typescript\nreadonly source: enum {\n  'api-gateway'\n}\n```\n\n---\n\n\u003ca name=\"api-request-timestamp\" /\u003e\n\n### request.timestamp\n\nTimestamp at the time of the first middleware's execution.\n\n```typescript\nreadonly timestamp: number\n```\n\n---\n\n\u003ca name=\"api-response-respondTo\" /\u003e\n\n### response.respondTo(formats, statusCode = 200, options = {}): void\n\nRespond according to request's Accept header with formats provided in `formats` map. Kind of like a Ruby on Rails `respond_to do |format|` [block](http://api.rubyonrails.org/classes/ActionController/MimeResponds.html#method-i-respond_to).\n\n```js\nresponse.respondTo({\n  json: {},\n  html: '\u003chtml /\u003e',\n})\n```\n\n---\n\n\u003ca name=\"api-response-raw\" /\u003e\n\n### response.raw(error: Error | null, result?: object | boolean | number | string): void\n\nExposes the underlying `callback` method.\n\n```js\nresponse.raw(null, { something: 'custom' })\n```\n\n## Error Handling\n\nThrow em. Alagarr will catch them.\n\n@TODO\n\n## Logging\n\nYes.\n\n@TODO\n\n## Middleware\n\nAlagarr uses a pipeline of middleware functions to process the incoming request and outgoing response objects. This lets you customize how your requests and responses are handled as well as provide custom middleware in addition to those provided by Alagarr.\n\n### Request Middleware\n\nAlagarr includes the following request middleware:\n\n| Provider | Name                                                                                 | Default  | Description                                                                                                                     |\n| -------- | ------------------------------------------------------------------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------- |\n| All      | [meta](src/request/middleware/meta.ts)                                               | built-in | Adds meta data about the request including whether the invocation is a coldStart, and invocation count                          |\n| All      | [normalize-headers](src/request/middleware/normalize-headers.ts)                     | built-in | Normalizes request headers.                                                                                                     |\n| All      | [normalize-programming-model](src/request/middleware/normalize-programming-model.ts) | built-in | Normalizes the programming models of different providers.                                                                       |\n| All      | [timestamp](src/request/middleware/timestamp.ts)                                     | built-in | Adds a request-start timestamp under `request.timestamp` which can be used to determine the ellapsed duration of the invocation |\n| Any      | [cookies](src/request/middleware/cookies.ts)                                         | enabled  | Parses cookies out of request header and makes them accessible under `request.cookies`                                          |\n| Any      | [hostname](src/request/middleware/hostname.ts)                                       | enabled  | Sets a convenience `hostname` property on the request object based on the request headers                                       |\n| Any      | [json-body](src/request/middleware/json-body.ts)                                     | enabled  | Body parser for request bodies with content-type of application/json                                                            |\n| Any      | [url-encoded-body](src/request/middleware/url-encoded-body.ts)                       | enabled  | Body parser for request bodies with content-type of application/x-www-form-urlencoded                                           |\n| AWS      | [base64-body](src/request/middleware/base64-body.ts)                                 | enabled  | Decodes base64-encoded request bodies when `isBase64Encoded` on the API Gateway request is truthy                               |\n\n### Response Middleware\n\nAlagarr includes the following response middleware:\n\n| Provider | Name                                                            | Default  | Description                                                                                                   |\n| -------- | --------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------- |\n| All      | [enforced-headers](src/response/middleware/enforced-headers.ts) | built-in |                                                                                                               |\n| All      | [log](src/response/middleware/log.ts)                           | built-in |                                                                                                               |\n| Any      | [compress](src/response/middleware/compress.ts)                 | disabled | Compress response body with deflate or gzip when appropriate                                                  |\n| Any      | [content-length](src/response/middleware/content-length.ts)     | enabled  | Adds a content-length header to the response                                                                  |\n| Any      | [csp](src/response/middleware/csp.ts)                           | enabled  | Adds [Content-Security-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) headers to the response |\n| Any      | [etag](src/response/middleware/etag.ts)                         | disabled | Adds an Entity Tag (ETag) header to the response                                                              |\n\n### Custom Middleware\n\nAll middleware are functions. Middleware which is included in Alagarr are all pure, but this is not required for custom middleware. Middleware may return Promises which are resolved before the next middleware is called. Middleware should not mutate state, but instead return new values—but this is not required in custom middleware. However, everytime middleware mutates state, a cute cuddly koala dies somewhere in Australia. So.. Yea.\n\nRequest middleware act on a request object and must always return a new request object. Request middleware have the following function signature:\n\n```typescript\ntype requestMiddleware = (\n  request: InterfaceRequest,\n  options: InterfaceAlagarrOptions,\n) =\u003e InterfaceRequest\n```\n\nResponse middleware act on the response object and must always return a new response object. Response middleware have the following function signature:\n\n```typescript\ntype responseMiddleware = (\n  response: InterfaceResponseData,\n  request: InterfaceRequest,\n  options: InterfaceAlagarrOptions,\n) =\u003e InterfaceResponseData\n```\n\n#### Example\n\nAn example of custom middleware might be middleware which handles user sessions. The request middleware would restore a session from some data store, while the response middleware might ensure a session is updated and a cookie is set.\n\n**Request Middleware**\n\n```javascript\nmodule.exports.restoreSession = async function(request) {\n  const { cookies: { sessionId } } = request\n  const session = (await getSessionFromDatabase(sessionId)) || undefined\n\n  return {\n    ...request,\n    session,\n  }\n}\n```\n\n**Response Middleware**\n\n```javascript\nmodule.exports.saveSession = async function(responsePayload, request) {\n  const sessionCookie = await saveSessionToDatabase(request.session)\n\n  return {\n    ...responsePayload,\n    headers: {\n      ...responsePayload.headers,\n      'Set-Cookie': `session=${sessionCookie}`, // @TODO: refactor once #5 is closed.\n    },\n  }\n}\n```\n\nThis custom middleware could then be used with Alagarr in a serverless function handler with:\n\n```javascript\nconst handler = require('alagarr')\nconst { restoreSession, saveSession } = require('./custom-middleware')\n\nconst alagarrConfig = {\n  requestMiddleware: ['default', restoreSession],\n  responseMiddleware: ['default', saveSession],\n}\n\nmodule.exports.userDashboardHandler = handler((request, response) =\u003e {\n    const session = request.session\n\n    if (!session) {\n      return response.redirect('/login')\n    }\n\n    return `\u003ch1\u003eWelcome back, ${session.username}!\u003c/h1\u003e`\n  }\n  alagarrConfig,\n)\n```\n\n## Contributing\n\nThe codebase _tries_ to follow declarative, functional(ish) programming paradigms. Many functional styles are enforced through TSLint linter utilised by the project. These include immutablity rules (`no-let`, `no-object-mutation`) and rules which prohibit imperative code (`no-expression-statement`, `no-loop-statement`). Disabling the linter for code should be avoided. Exceptions are made where satisfying a linting rule is impractical or otherwise untenable. In practice, this tends to be areas where the code touches 3rd party modules and in tests due to Jest's imperative-style.\n\n## Similar Projects\n\n* [Middy](https://github.com/middyjs/middy)\n* [corgi](https://github.com/balmbees/corgi)\n* [node-lambda-req](https://github.com/doomhz/node-lambda-req)\n* [apigateway-utils](https://github.com/silvermine/apigateway-utils)\n* [serverless-utils](https://github.com/silvermine/serverless-utils)\n* [@graphcool/lambda-helpers](https://www.npmjs.com/package/lambda-helpers)\n\n## Related Thingies\n\n* [aws-kms-thingy](https://github.com/adieuadieu/aws-kms-thingy)\n* [aws-s3-thingy](https://github.com/adieuadieu/aws-s3-thingy)\n\n## License\n\n**Alagarr** © [Marco Lüthy](https://github.com/adieuadieu). Released under the [MIT](./LICENSE) license.\u003cbr\u003e\nAuthored and maintained by Marco Lüthy with help from [contributors](https://github.com/adieuadieu/alagarr/contributors).\n\n\u003e [github.com/adieuadieu](https://github.com/adieuadieu) · GitHub [@adieuadieu](https://github.com/adieuadieu) · Twitter [@adieuadieu](https://twitter.com/adieuadieu) · Medium [@marco.luethy](https://medium.com/@marco.luethy)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadieuadieu%2Falagarr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadieuadieu%2Falagarr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadieuadieu%2Falagarr/lists"}