{"id":16255213,"url":"https://github.com/qubyte/szalinski","last_synced_at":"2025-10-08T12:43:57.950Z","repository":{"id":66105590,"uuid":"72283430","full_name":"qubyte/szalinski","owner":"qubyte","description":"An image resizing service, written in Node.js with Toisu! and backed by an LRU redis cache.","archived":false,"fork":false,"pushed_at":"2020-05-30T04:09:44.000Z","size":283,"stargazers_count":6,"open_issues_count":14,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-03T11:11:17.615Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","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/qubyte.png","metadata":{"files":{"readme":"README.md","changelog":"HISTORY.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","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":"2016-10-29T12:26:48.000Z","updated_at":"2022-04-21T07:41:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"44509c76-e244-4a04-aea2-f21dc7f17268","html_url":"https://github.com/qubyte/szalinski","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/qubyte/szalinski","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qubyte%2Fszalinski","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qubyte%2Fszalinski/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qubyte%2Fszalinski/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qubyte%2Fszalinski/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qubyte","download_url":"https://codeload.github.com/qubyte/szalinski/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qubyte%2Fszalinski/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":278947971,"owners_count":26073736,"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","status":"online","status_checked_at":"2025-10-08T02:00:06.501Z","response_time":56,"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":[],"created_at":"2024-10-10T15:28:52.123Z","updated_at":"2025-10-08T12:43:57.921Z","avatar_url":"https://github.com/qubyte.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# szalinski\n\n[![Greenkeeper badge](https://badges.greenkeeper.io/qubyte/szalinski.svg)](https://greenkeeper.io/)\n\nAn image resizing service, written in Node.js with Toisu! and backed by an LRU\nredis cache. This service was written as a technical demo for a job application,\nand now serves as an example Toisu! service.\n\n## Usage\n\nThis application behaves as intended when the redis instance it pairs with is\nconfigured as an LRU cache. A [docker-compose](docker-compose.yml) file is\nprovided to run both this application and such a redis instance. In order to\nfollow the instructions below, you must have docker and docker compose\ninstalled.\n\nClone this repository and run\n\n```\ndocker-compose up\n```\n\nin the repository directory from a terminal to start the app and redis. Navigate\nto `127.0.0.1:8080/resize?url=\u003can image url\u003e\u0026width=\u003cdesired width\u003e`. Only one of\nwidth or height is required, but both may be given (the app will use the most\nconstraining and maintain aspect ratio).\n\nIf you wish to run szalinski without docker, you may use Node.js v7.6 or up, and\nmust have a redis instance running. If the redis instance is running on the same\nmachine, the default configuration of the app will suffice and you can boot\nusing\n\n```\nnode .\n```\n\n## Configuration\n\nThe app may be configured by environment variables.\n\n| option    | environment variable   | default     |\n| --------- | ---------------------- | ----------- |\n| appPort   | `SZALINSKI_APP_PORT`   | `8080`      |\n| logLevel  | `SZALINSKI_LOG_LEVEL`  | debug       |\n| redisHost | `SZALINSKI_REDIS_HOST` | `127.0.0.1` |\n| redisPort | `SZALINSKI_REDIS_PORT` | `6379`      |\n\n## Test coverage:\n\nMost test modules have been written. The tests for\n[`getOriginal`](test/middleware/getOriginal.tests.js) and\n[`getResized`](test/middleware/getResized.tests.js) take a behavioural approach\nwhich requires redis to be running.\n\n - [ ] index.js\n - lib\n  - [ ] app.js\n  - [x] config.js\n  - [x] logger.js\n  - [x] redisClient.js\n - middleware\n  - [x] calculateResizedDimensions.js\n  - [x] getOriginal.js\n  - [x] getResized.js\n  - [x] parseAndValidateQuery.js\n  - [x] ping.js\n  - [x] requestLogger.js\n  - [x] responseTime.js\n  - [x] sendImage.js\n\n## Notes\n\n### The name\n\nThe name is a reference to Honey, I Shunk the Kids.\n\n### Toisu!\n\n[Toisu!](https://github.com/qubyte/toisu) is a small server framework I put\ntogether as an experiment into replacing Express with something built to use\npromises. The result is something that still uses middleware functions, but\nthese functions now only have request and response arguments, much like a\nvanilla Node request handler function. The `next` callback goes away because\nsynchronous middleware just returns undefined, and asynchronous middleware\nreturns a promise. This is especially fun when using async-await is to build\nmiddleware.\n\nToisu! borrows the idea of a shared context from Koa. Unlike Koa, the shared\ncontext is a `Map` instance (which may be used as a plain object) and provides\nnothing out of the box. The intention is that middleware functions use this as a\nway to communicate with and provide methods for later middleware.\n\nToisu! does not include routing out of the box. This is provided by\n`toisu-router`. This router was originally developed for REST APIs (it provides\nautomatic 405 responses for example). As it has only been used for REST, I\nhad not realised that it handled query parameters improperly. This exercise led\nto [a fix for that issue](https://github.com/qubyte/toisu-router/pull/7).\n\n### Clustering\n\nClustering has been omitted since the app is intended to run via docker in a\ncluster.\n\n### Config\n\nI used a module called [configeur](https://github.com/qubyte/configeur), which\nI built to parse environment variables and provide defaults. In the spirit of\n12-factor apps.\n\n### Logging\n\nI've used bunyan for logging. Each log entry is a single line and parsable as\nJSON. Having the log all on one line is handy for grepping, and lots of context\ncan be stored in the JSON snippet. In the case of this app, each\nrequest-response cycle gets its own UUID, and this is injected into each log\nentry from the cycle.\n\nTo pretty-print logs, pipe them to the bunyan cli utility.\n\n### Image processing\n\nI've used `sharp` and `image-type` for processing images. The latter can\ndetermine the type of an image from a buffer, and the former can be used to do\nthe image resizing.\n\n### Making requests\n\nI've used node-fetch, since it has a simple API and prodides a response method\nwhich resolves to a buffer. It is also promise based, which makes it a good fit\nwith the server framework.\n\n### Redis\n\nThe redis server is configured as a LRU cache. All data for each image is stored\nin a redis hash, so accessing the original or any resized version of an image\nwill encourage redis not to drop data for that URL when the database becomes\nfull.\n\nI've stored images as base64 encoded strings. Each image URL is hashed, and that\nhash used as a key of a redis hash. The original image is set on the `original`\nkey of the hash, and resized versions set on a key formatted as\n`\u003cwidth\u003e:\u003cheight\u003e`. Other metadata of the original image stored in redis include\nwidth, height, mime-type, and URL.\n\nThe client module I've used is ioredis, which provides a nice promise based API.\n\n### Potential improvements\n\nThis service keeps entire buffers in memory. Streams would likely allow buffers\nto be piped to the client to avoid memory consumption per request.\n\nWhen two requests for the same image, or same new size of an image, come in at\nthe same time, the image will be processed and cached twice. This can be avoided\nwith the use of a lock and redis pubsub.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqubyte%2Fszalinski","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqubyte%2Fszalinski","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqubyte%2Fszalinski/lists"}