{"id":16254293,"url":"https://github.com/celer/hopjs","last_synced_at":"2026-03-07T01:02:41.202Z","repository":{"id":4719265,"uuid":"5867450","full_name":"celer/hopjs","owner":"celer","description":"Restful API Framework","archived":false,"fork":false,"pushed_at":"2014-10-08T05:17:18.000Z","size":2571,"stargazers_count":12,"open_issues_count":0,"forks_count":1,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-10-31T08:51:31.852Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/celer.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}},"created_at":"2012-09-19T05:36:18.000Z","updated_at":"2018-06-05T23:08:01.000Z","dependencies_parsed_at":"2022-06-25T21:43:25.042Z","dependency_job_id":null,"html_url":"https://github.com/celer/hopjs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/celer/hopjs","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fhopjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fhopjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fhopjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fhopjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/celer","download_url":"https://codeload.github.com/celer/hopjs/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/celer%2Fhopjs/sbom","scorecard":{"id":270477,"data":{"date":"2025-08-11","repo":{"name":"github.com/celer/hopjs","commit":"5ffa81857b2131209e88ad007dd065779e528ec2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Code-Review","score":0,"reason":"Found 1/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":["Info: Possibly incomplete results: error parsing shell code: case patterns must consist of words: gen/shell/template.sh:0","Info: Possibly incomplete results: error parsing shell code: a command can only contain words and redirects; encountered (: gen/shell/testTemplate.sh:0"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 1 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-17T13:12:25.314Z","repository_id":4719265,"created_at":"2025-08-17T13:12:25.315Z","updated_at":"2025-08-17T13:12:25.315Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":30204452,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-06T19:07:06.838Z","status":"ssl_error","status_checked_at":"2026-03-06T18:57:34.882Z","response_time":250,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"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:21:01.312Z","updated_at":"2026-03-07T01:02:41.070Z","avatar_url":"https://github.com/celer.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"![alt text][logo]\n\n[![Build Status](https://travis-ci.org/celer/hopjs.png)](https://travis-ci.org/celer/hopjs)\n[![Depdendency Status](https://david-dm.org/celer/hopjs.png)](https://david-dm.org/celer/hopjs)\n\n# HopJS \n\nThe RESTful API dynamic web apps crave.\n\n## Introduction\n\nHopJS is a RESTful based declarative API framework for Node.js that:\n  * Can generate native APIs for Android, iPhone, iPad, JavaScript, and Shell\n  * Generates easy to use browser side API hooks\n  * Has a declarative testing interface, which can generate native unit tests in JavaScript and native API frameworks\n  * Generates its own API documentation\n  * Supports intelligent server-side caching of results using Redis\n  * Supports event based APIs using Socket.io \n  * Enhanced APIs with optional declarative models\n\n[API Documentation](http://celer.github.com/hopjs/doc/)\n\n*First, we simply define the interface you wish to expose*\n(either as static methods on an object or as a proper JavaScript class)\n```javascript\n\nUserService={};\n\n//All functions backed by HopJS should have a signature of (input,onComplete,request)\nUserService.create=function(input,onComplete){\n  // Here we would create a new user and call onComplete(err,result) when were done\n}\n\nUserService.authenticate=function(input,onComplete){\n  // Here we would authenticate a user and call onComplete(err,result) when were done\n}\n\n```\n*Next, we use Hop to define the interface; this will expose the interface via a RESTful API*\n\n```javascript\n\n//This will create a RESTful set of URLs which expose the following functions:\nHop.defineClass(\"UserService\",UserService,function(api){\n  api.post(\"create\",\"/user\").demand(\"email\").demand(\"username\");\n  api.post(\"authenticate\",\"/user/auth\").demand(\"email\").demand(\"username\");\n});\n\n//Now tell HopJS to expose our API in express.js\nHop.exposeAPI(\"/api/\",app)\n\n```\n\nNow that we've done that we get a few things:\n * We have our RESTful API\n * HopJS generates a client side API we can use in our browser which will have the following definitions:\n   * UserService.create(input,onComplete)\n   * UserService.authenticate(input,onComplete)\n\nSo now our web-site has:\n```shell\n  # An API for UserService.create \n  POST /api/user\n  # An API for UserService.authenticate\n  POST /api/user/authenticate\n  # Documentation for our API as generated by HopJS with online unit tests\n  GET /api/ \n  # A jQuery based client set of stubs for our API\n  GET /api/api.js\n  # A JSON definition of our API for client side stub generation\n  GET /api/api.json\n```\n[defineClass documentation](http://celer.github.com/hopjs/doc/classes/Hop.Method.html)\n\n*But we can also define the test cases for our new interface!*\n\n```javascript\n\nHop.defineTestCase(\"UserService.authenticate\",function(test){\n    var validUser = { email:\"test@test.com\", username:\"TestUser\" };\n    test.do(\"UserService.create\").with(validUser).noError();\n    test.do(\"UserService.authenticate\").with(validUser).noError();\n    test.do(\"UserService.authenticate\").with({password:\"BOB\"},validUser).hasError(/Permission denied/);\n});\n\n```\n[defineTestCase documentation](http://celer.github.com/hopjs/doc/classes/Hop.TestTask.html)\n\n### Testing from the command line\n\n*We can unit test our API using the hopjs utility, which will run all the unit tests from the command line:*\n```shell\nnpm install hopjs-remote -g\nhopjs --url http://localhost:3000/ --unitTest\n```\n*We can also run the test in the browser of our choosing*\n\n```shell\nhopjs-browser-test --url http://localhost:3000/  --browser firefox\n```\n\n### Generating native Android APIs \n\n(This is still a work in progress)\n\n*Now let's suppose we wanted an Android set of native client stubs for our API in Java:*\n\n```shell\nhopjs-gen --url http://www.website.com:3000/ android --outputDir ./androidApp --package com.website.www --apiVersion 1.0\n```\n\n### Generating native iOS APIs\n\n*Now let's assume we wanted a native version of the APIs for iOS, and you have OSX and XCode installed:*\n\n```shell\nhopjs-gen --url http://localhost:3000/ apple --type iostest --outputDir IOSTest --projectName IOSTest\ncd IOSTest\nmake\nopen IOSTest.xcworkspace\n# On the top left of xcode select \"IOSTest \u003e iPhone X Simulator\" and click the 'Run' button\n# If this fails in the project view select *.storyboard and delete the references from the project and re-add them. \n# After that it should just work! \n```\n![Image of iphone showing IOSTest][iphone]\n\nYou can read more about Objective-C APIs here: https://github.com/celer/hopjs/tree/master/gen/apple\n\n### Running the example\n\nYou can see a complete working example at: https://github.com/celer/hopjs/tree/master/examples/intro\n\n## Intelligent server-side caching of results\n\nNow let's assume that we've written a killer server-side API, but we haven't done any caching of our results so each \ntime we need to do something we're hitting our database. HopJS has the ability to add caching on top of your API quickly\nand easily.\n\n```javascript\n\n  /* \n      First let's tell HopJS that we want to use caching  \n      log - log what is happening with our server side cache\n      redisClient - the redisClient to use - HopJS will create a default one if not specified\n  */\n  Hop.enableCaching({ log:true, redisClient: myRedisClient });\n \n   \n  Hop.defineClass(\"UserService\",UserService,function(api){\n    api.usage(\"Manages users\");\n   \n    //Cache users as they are loaded for 60 seconds, and try to force the client to cache the results as well!\n    api.get(\"load\",\"/user/:id\").demand(\"id\").cacheId(\"/user/:id\",60,true);\n   \n    //Invalidate the cache when a user is deleted \n    api.del(\"delete\",\"/user/:id\").demand(\"id\").cacheInvalidate(\"/user/:id\");\n    \n    //Cache the search results for 5000 seconds\n    api.get(\"list\",\"/user/\").optional(\"sortBy\").cacheId(\"/users/:start/:size/\",5000).demand(\"start\").demand(\"size\");\n    \n    \n  });\n\n```\n\nCaching works by associating a unique ID with each result returned from an API call - the trick is that the ID is calculated based upon the object that is used as an input or returned as a result of calling the API call. \n\nTime for a quick example:\n\n\n```javascript\n  /* \n    Assuming the requested user was found Redis would return the cached object, otherwise HopJS would\n    call the underlying API, and also keep a copy of the returned object\n    under the id '/user/5' for a duration of 60 sections. HopJS would also add all the extra headers\n    to get the HTTP agent on the other end to cache this result for the specified duration as well!\n\n    Essentially\n      - If we have the cached object return it\n      - If not execute the call and cache the result \n\n    The id for the cached object is generated by plugging the result objects properties into \"/user/:id\" to compute \"/user/5\"\n\n  */\n  UserService.load({id:5}) \n\n  /*\n    This would end up deleting the cached object from Redis\n  */\n  UserService.del({id:5})\n```\n\nYou can see a complete working example at: https://github.com/celer/hopjs/tree/master/examples/caching\n\n## API Interfaces\n\nHopJS also has the ability to define an API interface which can be used to quickly stub out APIs which share their interfaces:\n\n```javascript\n\n  // This will define an interface which can the be applied to other objects later\n  Hop.defineInterface(\"Notification\",function(api){\n      //#classname will cause the classname of the extending class to be substituted into the path\n      api.post(\"send\",\"#classname/send\").usage(\"Sends a message\").demand(\"msg\").demand(\"subject\").demand(\"to\");\n  });\n\n  Hop.defineClass(\"Email\",EmailService,function(api){\n    //This will cause the interface defined above to be applied to this object\n    // Now EmailService.send will exist on /email/send with all the associated demands, etc.\n    api.extend(\"Notification\");\n  });  \n\n```\nYou can see a complete working example at: https://github.com/celer/hopjs/tree/master/examples/interface\n\n## Working with files\n\nWorking with files is pretty simple! To send files we can simply tell HopJS how to send the file, either as a raw file, or as an attachment. We can \nalso allow uploads using the .demandFile or the .optionalFile\n\n```javascript\n\tFileTest.sendFile=function(input,onComplete){\n\t\treturn onComplete(null,Hop.sendFile(\"public/pig.png\"));\n\t}\t\n\n\tFileTest.sendAttachment=function(input,onComplete){\n\t\treturn onComplete(null,Hop.sendAttachment(\"public/pig.png\",\"image.png\"));\n\t}\n\n\tFileTest.upload=function(input,onComplete){\n\t\treturn onComplete(null,input);\n\t}\t\n\n\n\tHop.defineClass(\"FileTest\",FileTest,function(api){\n\t\tapi.get(\"sendFile\",\"/file\")\n\t\tapi.get(\"sendAttachment\",\"/attachment\");\n\t\tapi.post(\"upload\",\"/upload\").demandFile(\"required\").optionalFile(\"optional\");\n\t});  \n```\nYou can see a complete working example at: https://github.com/celer/hopjs/tree/master/examples/files\n\n## Models\n\nModels can be defined which will enable both validation of inputs but re-use of documentation and type conversion.\n\n```javascript\n\tHop.defineModel(\"User\",function(user){\n\t\tuser.field(\"id\",\"UserID\",\"The user's id\").integer().ID();\n\t\tuser.field(\"name\",\"Username\",\"The user's username\").string().regexp(/[A-Za-z0-9\\_\\-]{3,10}/,\"Usernames must be between 3 and 10 characters long, and can only contain alphanumeric characters\");\n\t\tuser.field(\"email\",\"Email\",\"The user's email address\").string();\n\t\tuser.field(\"password\",\"Password\",\"The user's password\").password();\n\t});\n```\nNow we can simply indicate a model is used for a call by using .useModel, .inputModel or .outputModel\n\n```javascript\n\tHop.defineClass(\"UserService\",UserService,function(api){\n\t\tapi.usage(\"Manages users\");\n\t\tapi.post(\"create\",\"/user\").usage(\"Creates a user\").demands(\"email\",\"name\",\"password\").useModel(\"User\");\n\t\tapi.post(\"authenticate\",\"/user/auth\").usage(\"Authenticates a user\").demands(\"password\",\"name\").useModel(\"User\");\n\t\tapi.get(\"currentUser\",\"/user\").usage(\"Returns the current user\").outputModel(\"User\");\n\t\tapi.get(\"logout\",\"/user/logout\").usage(\"Logs the current user out\");\n\t\tapi.del(\"del\",\"/user/:id\").usage(\"Deletes the user\").demand(\"id\").inputModel(\"User\");\n\t});\n```\n\nYou can see a complete working example at: https://github.com/celer/hopjs/tree/master/examples/model\n\n# How to use Hop with forms\n\nHop has some built-in utility functions to make it very easy to use with forms. On each function defined in the browser client side two functions exist, fromForm and toForm which can be used to submit a form\nas an input to a Hop function or to populate a form with the result of a function call. For example:\n\n```javascript\n\nUserService.create.fromForm(\"#userForm\",function(err,result){\n\n});\n\n```\nOr \n\n```javascript\nUserService.load.toForm({ id: 3},\"#userForm\",function(err,res){\n\n});\n\n```\n\nThese functions expect the form to have form elements with the same names as the various inputs to the functions, and also attempt to play nicely with bootstrap based forms, specifically by setting errors for various\nform elements and providing a generic error capability. This functionality is extremely powerful when combined with the models described above. See https://github.com/celer/hopjs/tree/master/examples/model for an example.\n\n\n# Notes about REST\n\n * Our implementation of REST is designed to be used with forms and does not support null values or special types, all values are converted to strings (null==\"\")\n * Per specification HTTP delete does not allow passing of parameters beyond what are specified in the path\n\n\n# Known Issues / Todo\n - iPhone API generation works but needs further testing\n - Android API generation works, testing and validation on it does not\n - A bug in combination-stream, which is utilized by request and form-data prevents the unit tests for experiments/test from passing, see my fork of combination-stream for a fix\n - Curl can't save session cookies so some shell tests won't work\n - Need to add SSL support\n - Need to add dev key support\n\n[logo]: https://raw.github.com/celer/hopjs/master/static/logo-200.png \n[iphone]: https://raw.github.com/celer/hopjs/master/static/iphone-small.png\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceler%2Fhopjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fceler%2Fhopjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fceler%2Fhopjs/lists"}