{"id":28459982,"url":"https://github.com/spacesoldier/starty","last_synced_at":"2026-05-07T13:34:37.473Z","repository":{"id":39452263,"uuid":"502026408","full_name":"spacesoldier/starty","owner":"spacesoldier","description":"a lightweight framework which helps building modular and configurable Node.js applications","archived":false,"fork":false,"pushed_at":"2022-08-14T02:25:31.000Z","size":163,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-03T00:36:47.978Z","etag":null,"topics":["ddd","functional-programming","javascript","nodejs","rapid-development","rapid-prototyping","rest-api"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spacesoldier.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":"2022-06-10T11:59:27.000Z","updated_at":"2022-06-10T15:07:40.000Z","dependencies_parsed_at":"2022-08-09T14:49:10.492Z","dependency_job_id":null,"html_url":"https://github.com/spacesoldier/starty","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/spacesoldier/starty","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacesoldier%2Fstarty","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacesoldier%2Fstarty/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacesoldier%2Fstarty/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacesoldier%2Fstarty/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spacesoldier","download_url":"https://codeload.github.com/spacesoldier/starty/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spacesoldier%2Fstarty/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":268326739,"owners_count":24232496,"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-08-02T02:00:12.353Z","response_time":74,"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":["ddd","functional-programming","javascript","nodejs","rapid-development","rapid-prototyping","rest-api"],"created_at":"2025-06-07T02:07:43.934Z","updated_at":"2026-05-07T13:34:32.449Z","avatar_url":"https://github.com/spacesoldier.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Starty\nA lightweight framework which helps to build modular and configurable Node.js applications\n\n## Features\n- automatic feature discovery\n- application configuration using Yaml\n- build a routing mechanics based on the provided configuration file\n- routing the requests from logic parts of custom features to external resources\n- logging\n\n## Getting started\nThe process of application development with Starty consists of several steps which could be done in any sequence:\n- [writing an application configuration](#Writing-the-configuration) in Yaml format\n- [writing the code](#Writing-the-application-logic) which implements your application requirements, feature by feature\n- let Starty discover your features at runtime and share them with the world according to the configuration\n\nThis scenario may look quite familiar for the programmers who may be new to Node.js based development, but have some previous experience with Java and one of the most popular Java frameworks - Spring Boot.\n\n### Installation\n```shell\nnpm i starty\n```\n\n### Writing the configuration\nYou may already have a vision of what do you plan to build. And some details, for example, a way of integration for your application with others in your product's landscape. Or it could be an early bird which acts as a prototype of a whole system. Or you just need to write a microservice with a limited set of a functions.\n\nAt the moment, Starty can help you to build a REST API and integrate with any other REST API.\nSo there could be defined two main sections in your `config.yml` file:\n\n- servers\n- clients\n\nAnd, of course, you can provide an application name. Let's take a look at the empty configuration template:\n```yaml\n# an application name, it will appear in logs on startup\napp-name: \"Starty simple app\"\n\n# this section describes the application logic in a form of services\n# it consists of servers and their endpoints definitions\nservers:\n\n# this section describes the usage of external services\n# by defining the resources to call and how to handle the results\nclients:\n\n\n```\n\u0026nbsp;\n#### Defining servers\nNext, let's configure the server. We need to set the `port` number to listen, `hosts` or IP addresses from where to receive the requests and the `protocol` it supports. Also, it might be useful to set the name of the server.\nWhen the server is defined, we can set up its endpoints.\n\n```yaml\n# a name of the server inside the application\nmy-http-server:\n  hosts:\n    - \"127.0.0.1\"\n  port: 8080\n  protocol: \"http\"\n  endpoints:\n```\nTalking about endpoints let's assume that one endpoint has several basic properties. It could correspond to a given `location` and receive the requests with one or more `method`s. And when it comes to request handling it's a good time to name the request handler functions. So we can state a method of the endpoint and corresponding request `handler` function by providing its name.\n\nAn example of an endpoint which serves the requests with one method could be a controller providing the static content on a `GET` request.\n```yaml\n# a name of the endpoint which serves static content\nserve-static:   # server name\n  location: \"/\"\n    methods:\n      get:\n        # the name of the function which handles the requests \n        # to the endpoint\n        handler: \"serveStatic\"\n```\n\nNext, an example of an endpoint which serves the requests with several methods we could imagine a session controller.\nSay, we could want it to start a new session on a `GET` request, validate an existing session on `POST` and terminate existing session on `DELETE` request.\n```yaml\n# an example of the endpoint which accepts the requests \n# with several methods\nsession:    # endpoint name\n  location: \"/session\"\n    methods:\n      get:\n        handler: \"newSession\"\n      post:\n        handler: \"validateSession\"\n      delete:\n        handler: \"deleteSession\"\n```\n\u0026nbsp;\n#### Defining clients\nWe have imagined a session controller. But it may not always know everything about the users and their roles.\nSo we can ask an external authorization service for help. Thus, we need to define a client for that purpose.\n\nTo allow the requests achieve their target API, several basic properties needed to be set.\nObviously, an `url` of the endpoint to call should be defined.\nIn some cases it could be needed to set up the `port`.\nOne of the important moments is to choose a `type` of the interaction with the external service.\nAt the moment, the only one type of clients available. \nIt is *web* client which can send requests using *http* or *https* protocols.\n\nNext, it's a good moment to think about the way the client may perform its operations.\nProbably it could be useful to imagine it has several `alias`es dedicated to the `methods` used for calling an external API endpoint.\nAnd we also need to know what to do when the external API call ended with `success` or with `fail` result by providing \nthe function names which handle these sorts of result. Thus, an example of a client may look  like following:\n```yaml\n# a client which purpose is to call an external API\nauth-api:   # client name\n  type: \"web\"\n  url: \"https://authorization-service/auth\"\n  port: 8080\n  methods:\n    post:\n      alias: \"authApiRequest\"\n      success: \"onAuthSuccess\"\n      fail: \"onAuthError\"\n```\n\u0026nbsp;\n#### Internal logic declaration\nLet's imagine you plan to implement some logic which is not directly connected with the REST API implementation.\nIt could be some post-processing or data aggregation stage.\nOn the contrary, it can be a very important part of the implementation of the service, its core, separated into its own logical layer.\n\nStarty allows to declare the internal parts of the logic in same style as it is done for the external resource clients.\nEven in more simple manner. To be able to route the messages to dedicated function `call`s\nit needs to know an `alias` of that function.\n\nAs an example we could imagine a function which counts how many users are online:\n\n```yaml\ninternals:\n  metrics:\n    alias: \"metricsCounter\"\n    call: \"countUsersOnline\"\n```\n\n\u0026nbsp;\n#### Scheduled events\nSometimes it is useful to perform some periodic operations. It could be sending some metrics, reports, etc.\nIn Node.js you can set up a timer for such a purpose. Starty helps to set up all timers in one place \nto keep an eye on the application structure.\nThe `schedule` section of the application config contains the timer definitions. Every timer has its own name, `alias`\nand a `call` which points to the name of the function which will be executed by the timer. \nThis scheduled function should be defined among other internal logic functions.\nAt this moment the `alias` of the timer is not important, but it could be used in future for managing timers. At least, cancelling or restarting them.\n\nFor our example case, let's show how many users are `online`, once a minute:\n\n```yaml\nschedule:\n  online:\n    alias: \"onlineReport\"\n    call: \"showUsersOnline\"\n    period: 60000   # milliseconds\n```\n\n\u0026nbsp;\n#### Environment variables\n\nYou can easily pass the environment variables into the application configuration. \nTo prevent any errors, the default values should be set. The syntax looks like this:\n```\n   ${ENVIRONMENT_VARIABLE:default value}\n```\n\nLet's look at an example of the server configuration. \nAssume the port number to listen and an IP address to accept the requests from could be set using the environment variables.\nSo the server configuration will look like this:\n```yaml\n# a name of the server inside the application\nmy-http-server:\n  hosts:\n    - ${ACCEPT_HOST:\"127.0.0.1\"}\n  port: ${PORT:8080}\n  protocol: \"http\"\n  endpoints:\n```\n\n\u0026nbsp;\n#### Bringing it all together\n\nAs a result, the example of an application configuration may look like following:\n\n```yaml\n# an application name, it will appear in logs on startup\napp-name: \"Starty simple app\"\n\n# this section describes the application logic in a form of services\n# it consists of servers and their endpoints definitions\nservers:\n  # a name of the server inside the application\n  my-http-server:\n    hosts:\n      - ${ACCEPT_HOST:\"127.0.0.1\"}\n    port: ${PORT:8080}\n    protocol: \"http\"\n    endpoints:\n\n      # a name of the endpoint which serves static content\n      serve-static:   # server name\n        location: \"/\"\n          methods:\n            get:\n              # the name of the function which handles \n              #  the requests to endpoint\n              handler: \"serveStatic\"\n  \n      # an example of the endpoint which accepts the requests \n      # with several methods\n      session:    # endpoint name\n        location: \"/session\"\n          methods:\n            get:\n              handler: \"newSession\"\n            post:\n              handler: \"validateSession\"\n            delete:\n              handler: \"deleteSession\"\n\n\n# this section describes the usage of external services\n# by defining the resources to call and how to handle the results\nclients:\n  # a client which purpose is to call an external API\n  auth-api:   # client name\n    type: \"web\"\n    url: ${AUTH_SRV_HOST:\"https://authorization-service/auth\"}\n    port: ${AUTH_SRV_PORT:8080}\n    methods:\n      post:\n        alias: \"authApiRequest\"\n        success: \"onAuthSuccess\"\n        fail: \"onAuthError\"\n\ninternals:\n metrics:\n  alias: \"metricsCounter\"\n  call: \"countUsersOnline\"\n\nschedule:\n online:\n  alias: \"onlineReport\"\n  call: \"showUsersOnline\"\n  period: 60000   # milliseconds\n\n```\n\u0026nbsp;\n### Writing the application logic\nWhen all the preparations are done it's time to write some application logic.\nIn previous sections we described a lot of stuff about the configuration and binding the connections \nto the functions which will handle the requests and prepare the requests for external API.\n\nAs it was said before, it is possible to start working on the project either with configuration step, or with logic implementation step.\nBut it's always useful to keep in mind the plan and the project structure.\n\nTalking about the project structure, let's take a look at it in terms of files and directories.\n\n#### Typical project structure\n\nAt the root level of the application directory the configuration file `config.yml` is located.\nThe starting point of the application `app.js` is also located here.\nWhen the application starts, the framework will perform a feature scan in the `features` directory.\n\nEvery feature implementation should be located in its own directory and provide the desired functions to the scope of the application\nusing the declaration in its `index.js` file. \n\nThus, the project directory structure may look like following:\n\n``` \n.\n├─── features\n│    ├─── feature1\n│    │    ├─── feature1.js\n│    │    └─── index.js\n│    ├─── feature2\n│    │    ├─── feature2.js\n│    │    └─── index.js\n│    .\n│    .\n│    .\n│    └─── featureN\n│         ├─── featureN.js\n│         └─── index.js\n├─── app.js\n├─── config.yml\n└─── package.json\n```\n\nThe contents of a feature directory could be more complex. In fact, it could contain any structure, probably a sort of project dedicated to this feature implementation.\nIn fact the one important thing is to share through `index.js` file only the certain functions needed for the application and nothing more.\nAs Starty uses `require` to discover these functions, all other stuff from the feature implementation directory will live in its own scope, providing limited amount of integration points to the application scope.\nYet another sort of open/closed principle in action, one may notice.\n\n\u0026nbsp;\n#### Entry point\nThe application entry point can be defined at the root level of an application directory.\nIn our example described above its file was named as `app.js`.\nLet's take a look at the contents of this file:\n\n```javascript\nconst {applicationStart} = require('starty');\n\napplicationStart('./config.yml');\n```\n\nLooks simple, isn't it? What happens here is the following: we load Starty using `require`,\n tell where to find a configuration file and then let it do the job by discovering and configuring features from the directory which contains the features code.\n\nThis approach could be useful when it comes to run your applications in containerized environment.\nFor example, you can pack only the entry point and *node_modules* directory and mount the configuration file and features code directories\nfrom the external source. Which allows to develop large applications with many features and separate them into multiple applications with limited functionality in relatively simple and painless manner. \n\n\u0026nbsp;\n#### Writing features\nNow let's talk about writing the code aimed to implement the desired application feature set.\nThe big idea of the development approach is to implement the blocks of technical requirements in separate modules called features for simplicity.\nOne feature may contain the set of functions providing logic for one or more endpoints and client connections to the external resources.\n\nTechnically it is possible to use the functions from different features building new features.\nThis may cause the dependencies which at the moment could not be tracked by the framework. \nSo, it is up to developer to track and maintain them when making a decision to separate the functionality of one application to multiple microservices.\n\nAnd now let's get down to writing some code. Assume the current task is to implement the features defined in configuration from the example provided in previous parts.\nThere could be two major features: `serve-static` and `session` management.\nLet's take a look at the possible project structure in this case:\n\n```\n.\n├─── features\n│    ├─── session\n│    │    ├─── session.js\n│    │    └─── index.js\n│    └─── serve-static\n│         ├─── data\n│         │    ├─── favicon.ico\n│         │    └─── index.html\n│         ├─── serve-static.js\n│         └─── index.js\n├─── app.js\n├─── config.yml\n└─── package.json\n```\n\nServing static content will be left for an exercise. Probably this feature will become a part of a framework in future. \nSo as an example we will implement a session management functionality. Or at least a prototype of it, located in session.js file. \nThis is by no means a production-ready code, provided just for the showcase\n\nWhen it comes to request handling, Starty will route the requests to the corresponding functions in your code. \nThe simplest request handler function could take no parameters, but must return an object which either contains a *payload*, \nor a field named by an input of a client connection according to the configuration.\n\n```javascript\n'use strict'\nconst crypto = require('crypto');\n\n// this version returns a response immediately\nfunction newSession(){\n    return {\n        payload: crypto.randomUUID()\n    }\n}\n\n```\n\nAlso, it is possible to set the response headers:\n\n```javascript\n'use strict'\nconst crypto = require('crypto');\n\n// this version returns a response immediately\nfunction newSession(){\n    \n    let session = {\n        token: crypto.randomUUID(),  // generate new token\n        validUntil: new Date + 7     // which is valid for a week\n    }\n    \n    let sessionDetails = {};\n    \n    sessionDetails.response = {};\n    sessionDetails.response.headers = {\n        'content-type': 'application/json'\n    };   \n    \n    sessionDetails.payload = JSON.stringify(session);\n    \n    return sessionDetails;\n}\n\n// share your code with Starty\nmodule.exports = {\n    newSession\n}\n```\nIn this case Starty will take the headers from the *response* property and write it with the *payload* into the response message.\nBut the simplest examples could not always be useful. In fact Starty provides a message envelope object\nas a parameter for every handler function. The message envelope can have several properties:\n- msgId - a unique identifier of the incoming message, provided always\n- request - an object which contains request *headers* and *query* parameters. These properties can be empty when none of them received\n- payload - the request body which could be empty in case of handling a GET request, for example\n\nYou can use a simple logging feature provided by the framework. \nIt helps to track events happened in user defined code in connection with the name of the code block as an event source and with the timestamp.\nSo, here is a bit more complex example with some logging usage:\n```javascript\nconst {loggerBuilder, logLevels} = require('starty');\nconst log = loggerBuilder()\n                        .name('session service')\n                        .level(logLevels.INFO)\n                    .build();\n\n/**\n *\n * @param   {{  msgId:    string,\n *              request:  {headers: {object}, query: {object}}, \n *              response: {object}, \n *              payload:  {object}\n *          }} msg\n *          \n * @returns {{  msgId:    string, \n *              request:  {headers: {object}, query: {object}}, \n *              response: {headers:{object}}, \n *              payload:  {object}\n *          }} msg\n */\nfunction newSession(msg){\n\n    if (msg.request.query !== undefined){\n     let queryStr = JSON.stringify(msg.request.query);\n     \n     // you can use simple logger provided by the framework\n     log.info(`query params:  ${queryStr}`);\n    }\n    \n    let session = {\n        token: crypto.randomUUID(),  // generate new token\n        validUntil: new Date + 7     // which is valid for a week\n    }\n    \n    // statusCode could be set here\n    // especially when it's needed to report any errors\n    msg.response.statusCode = 200;\n    \n    // setting the response headers\n    msg.response.headers = {\n        'content-type': 'application/json'\n    };   \n    \n    // provide the output data\n    msg.payload = JSON.stringify(session);\n    \n    return msg;\n}\n\n// share your code with Starty\nmodule.exports = {\n    newSession\n}\n\n```\n\nAn example output of the logger which could be found in console when a request with query params arrived:\n\u003e [2022-06-24T05:57:24.661Z] [INFO] [session service] query params:  {\"foo\":\"bar\",\"fizz\":\"buzz\"}\n\nThe next and probably the last example will be a function which initiates the external resource call.\nLet's imagine we need the session controller to get the new token from an external API:\n\n```javascript\n'use strict'\n\n/**\n *\n * @param   {{  msgId:    string,\n *              request:  {headers: {object}, query: {object}}, \n *              response: {object}, \n *              payload:  {object}\n *          }} msg\n *\n * @returns {{  msgId:    string, \n *              request:  {headers: {object}}, \n *              response: {headers:{object}}, \n *              authApiRequest:  {object}\n *          }} msg\n */\nfunction newSession(msg){\n    // just for the illustration purpose\n    // let's say the client provided some parameters in query\n    let authParams = msg.request.query;\n      \n    // fill a request to external service instead\n    // assuming all needed information was provided \n    // in request query params\n    msg.authApiRequest = {\n     ...(authParams)\n    };\n   \n    // we do not plan to return a result here\n    // so we remove the 'payload' property \n    // from the outgoing message\n    delete msg.payload;\n    \n    // setting proper headers for external API request\n    // replacing originally received headers\n    msg.request = {\n     headers: {\n      'content-type': 'application/json'\n     }\n    };\n   \n    return msg;\n}\n\nmodule.exports = {\n                    newSession\n                  }\n```\nIn this case, the outgoing message will be routed to the external resource\naccording to the property name which corresponds to the alias of the client connections\ndefined in the configuration file.\n\nAfter the receiving any results from the external resource Starty will forward them \nto the corresponding handler function in same manner. \nThe result of the call will be written into the *payload* property of the message.\nThe message will then be provided to the handler function which deals with *success* or *fail* results of the call.\n\nThe handler functions are written in same style with same signatures.\nAccording to current the example case, let's take a look at the authorization results handler.\nAssume the new session opened successfully and needed details are provided in `msg.payload` field.\nAt this point we could decide to provide these details to user, but also increase the number of users online.\nThis decision may touch two zones of responsibility: one for receiving the details about new sessions and another for dealing with user counting.\n\nAlso let's look at the scheduled function calls. According to the configuration described in previous chapter,\nthere is one call which purpose is to report the current state of the online users counter.\nWhen firing a timed event Starty provides a *Date* object as `msg.payload`. \nThe result of a scheduled call could be routed to any internal or external receiver by the same rules as described above. \n\nThe example code which illustrates all these ideas could look like the following:\n```javascript\n'use strict'\nconst {loggerBuilder, logLevels} = require('starty');\nconst log = loggerBuilder()\n                        .name('session service')\n                        .level(logLevels.INFO)\n                  .build();\n/**\n *\n * @param   {{  msgId:    string,\n *              request:  {headers: {object}, query: {object}}, \n *              response: {object}, \n *              payload:  {object}\n *          }} msg\n *\n * @returns {{  msgId:    string, \n *              request:  {headers: {object}}, \n *              response: {headers:{object}}, \n *              metricsCounter:  {object}\n *          }} msg\n */\nfunction onAuthSuccess(msg){\n    \n    // let's forward the message to the users counting function\n    msg.metricsCounter = msg.payload;\n    delete msg.payload;\n    \n    return msg;\n}\n\n// do not try this at home\n// please :)\nlet usersCount = 0;\n\n/**\n *\n * @param   {{  msgId:    string,\n *              request:  {headers: {object}, query: {object}}, \n *              response: {object}, \n *              payload:  {object}\n *          }} msg\n *\n * @returns {{  msgId:    string, \n *              request:  {headers: {object}}, \n *              response: {headers:{object}}, \n *              payload:  {object}\n *          }} msg\n */\nfunction countUsersOnline(msg){\n    \n    log.info(`hey, one more user online!`);\n    \n    usersCount += 1;\n    \n    return msg;\n}\n\n/**\n *\n * @param   {{  msgId:    string,\n *              [request]:  {headers: {object}, query: {object}}, \n *              [response]: {object}, \n *              payload:  {Date}\n *          }} msg\n *\n * @returns {{  msgId:    string, \n *              [request]:  {headers: {object}}, \n *              [response]: {headers:{object}}, \n *              payload:  {object}\n *          }} msg\n */\nfunction showUsersOnline(msg){\n    \n    let now = msg.payload.toISOString();\n    \n    log.info(`Currently at ${now} there are ${usersCount} users online`);\n    \n    return msg;\n}\n\nmodule.exports = {\n                     onAuthSuccess,\n                     countUsersOnline,\n                     showUsersOnline\n                 }\n\n```\n\nAfter receiving a result, the message will be routed to users counter and then it will be routed back to the user side.\nAt the moment, Starty allows to build only consecutive call chains.\nThis definitely will be changed and improved in next versions of the framework\n\nThe authorization error response handler will be left for an exercise to the curious reader\n\n\u0026nbsp;\n## Example project:\n\nThe example project which uses Starty for demonstration purposes could be found here:\n(the full demonstration example coming soon, so it's just an old version)\nhttps://github.com/spacesoldier/messageBridgeServicePrototype\n\n\u0026nbsp;\n## Version history\n\n0.1.11 - correctly terminating the startup sequence in case of error in user defined code; correctly show the logging level in log messages\n\n0.1.10 - fixed an error with decorating features caused by out of sync library development process\n\n0.1.9 [deprecated] - introducing scheduling the events by timer in milliseconds. Start using a word `alias`\ninstead of `input` for internal functions in application configuration\n\n0.1.8 - implemented internal logic declaration as a bits of code which could be used as a separate logic level\n\n0.1.7 - enable setting URLs as environment variables default values in config.yml\n\n0.1.6 - fixed an error handling case when *msg.payload* is not a string (will improve later)\n\n0.1.5 - fixed a bug when request processing fails at the beginning\n\n0.1.4 [deprecated] - environment variables support in application config\n\n0.1.3 - returning status code 200 automatically only in case when it was not provided by user-defined code\n\n0.1.2 - added the support for the query parameters, fixed some bugs and finished the idea of separation the request handling by the framework and message processing by the user defined code\n\n0.1.1 [deprecated] - the very first draft, usable for building simple REST API which could call external API with GET and POST methods\n\n## Contacts\nI appreciate any feedback, so please feel free to write me an email: \nsilviosalgari@gmail.com","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspacesoldier%2Fstarty","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspacesoldier%2Fstarty","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspacesoldier%2Fstarty/lists"}