{"id":16213594,"url":"https://github.com/glebbatykov/minerva","last_synced_at":"2025-10-10T21:08:19.171Z","repository":{"id":46904982,"uuid":"515749600","full_name":"GlebBatykov/minerva","owner":"GlebBatykov","description":"Dart backend framework. Supports multithreading, websockets. It contains project build system, request processing pipeline, authentication aids and logging tools.","archived":false,"fork":false,"pushed_at":"2023-10-02T23:55:29.000Z","size":980,"stargazers_count":57,"open_issues_count":0,"forks_count":4,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-06-11T11:24:20.273Z","etag":null,"topics":["backend","dart","framework","http","minerva","multithreading","rest","routing","server","websocket","websocket-server"],"latest_commit_sha":null,"homepage":"","language":"Dart","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/GlebBatykov.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"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}},"created_at":"2022-07-19T21:43:42.000Z","updated_at":"2025-01-13T15:43:10.000Z","dependencies_parsed_at":"2024-01-16T12:01:26.731Z","dependency_job_id":null,"html_url":"https://github.com/GlebBatykov/minerva","commit_stats":{"total_commits":298,"total_committers":2,"mean_commits":149.0,"dds":0.003355704697986628,"last_synced_commit":"5bb915a96943a632c261e15aa9d347437bca905a"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"purl":"pkg:github/GlebBatykov/minerva","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlebBatykov%2Fminerva","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlebBatykov%2Fminerva/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlebBatykov%2Fminerva/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlebBatykov%2Fminerva/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GlebBatykov","download_url":"https://codeload.github.com/GlebBatykov/minerva/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GlebBatykov%2Fminerva/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279005407,"owners_count":26083883,"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-10T02:00:06.843Z","response_time":62,"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":["backend","dart","framework","http","minerva","multithreading","rest","routing","server","websocket","websocket-server"],"created_at":"2024-10-10T11:06:35.871Z","updated_at":"2025-10-10T21:08:19.141Z","avatar_url":"https://github.com/GlebBatykov.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\" width=\"200px\"\u003e\n\n\u003cimg src=\"https://raw.githubusercontent.com/GlebBatykov/minerva/main/doc/images/logo.png\" width=\"600px\"/\u003e\n\n\u0026nbsp;\n\nDart backend framework\n\n\u003c/div\u003e\n\n\u003cdiv align=\"center\"\u003e\n\n[![pub package](https://img.shields.io/pub/v/minerva.svg?label=minerva\u0026color=blue)](https://pub.dev/packages/minerva)\n\n**Languages:**\n  \n[![English](https://img.shields.io/badge/Language-English-blue?style=?style=flat-square)](README.md)\n[![Russian](https://img.shields.io/badge/Language-Russian-blue?style=?style=flat-square)](README.ru.md)\n\n\u003c/div\u003e\n\n- [About Minerva](#about-minerva)\n- [Ecosystem](#ecosystem)\n- [Installing](#installing)\n- [CLI](#cli)\n  - [Create project](#create-project)\n  - [Build project](#build-project)\n  - [Run project](#run-project)\n  - [Debug](#debug)\n    - [VS Code](#vs-code)\n  - [Testing](#testing)\n  - [Docker](#docker)\n- [Project configuration](#project-configuration)\n- [Framework structure](#framework-structure)\n  - [Components](#components)\n- [Routing](#routing)\n  - [Request processing pipeline](#request-processing-pipeline)\n  - [Endpoints](#endpoints)\n    - [Api](#api)\n    - [Controllers](#controllers)\n    - [Request body](#request-body)\n      - [FormData](#formdata)\n    - [Path parameters](#path-parameters)\n    - [Request filter](#request-filter)\n    - [Websockets](#websockets)\n- [Authentication](#authentication)\n  - [JWT](#jwt)\n  - [Cookie](#cookie)\n- [Middlewares](#middlewares)\n  - [Ready-made middlewares](#ready-made-middlewares)\n  - [Custom middlewares](#custom-middlewares)\n- [Files](#files)\n  - [Uploading files](#uploading-files)\n  - [Downloading files](#downloading-files)\n  - [Static files](#static-files)\n- [Dependency injection](#dependency-injection)\n- [Agents](#agents)\n  - [Custom agents](#custom-agents)\n- [Logging](#logging)\n  - [Pipeline](#pipeline)\n  - [Ready-made loggers](#ready-made-loggers)\n    - [Logging templates](#logging-templates)\n    - [Logging to files](#logging-to-files)\n  - [Logging configuration](#logging-configuration)\n  - [Custom loggers](#custom-loggers)\n- [Configuration manager](#configuration-manager)\n- [Deployment](#deployment)\n  - [Docker container](#docker-container)\n- [Road map](#road-map)\n\n# About Minerva\n\n`Minerva` is framework for creating multithreaded REST API.\n\n`Minerva` provides:\n\n- project build system;\n- multithreaded request processing;\n- routing requests, processing requests using middlewares;\n- logging capabilities, creating your own loggers;\n- provides means for authentication by `JWT`, `cookies`;\n- tools for distributing static files;\n- working with `FormData`;\n- ability to generate `Dockerfile`;\n- and another.\n\n# Ecosystem\n\nVarious packages to simplify working with `Minerva`, as well as in general that simplify writing server applications on `Dart`, are likely to be released as separate packages.\n\nCurrently existing my packages that may be useful to you:\n\n- [emerald](https://pub.dev/packages/emerald) - `JSON` serializer/deserializer, based on `dart:mirrors`, works only with `JIT` compilation type;\n- [mcache](https://pub.dev/packages/mcache) - package for caching values. Supports deleting values after their expiration date;\n- [minerva_mcache](https://pub.dev/packages/minerva_mcache) - package for integration [mcache](https://pub.dev/packages/mcache) package in the `Minerva` framework. Contains tools that allow you to work from different server instances with shared cache located in the agent;\n- [minerva_controller_generator](https://pub.dev/packages/minerva_controller_generator) - the package allows you to configure the server using controllers built on code generation.\n\n# Installing\n\nInstall `Dart`.\n\nInstalling `Minerva`:\n\n```dart\ndart pub global activate minerva\n```\n\nCreate project with standart template (controllers) and run example:\n\n```dart\nminerva create -n my_application\n\ncd my_application\n\ndart pub run build_runner build\n\nminerva run\n```\n\nCreate project with endpoints template and run example:\n\n```dart\nminerva create -n my_application -t endpoints\n\ncd my_application\n\ndart pub run build_runner build\n\nminerva run\n```\n\n# CLI\n\nThe framework contains the `CLI` utility `Minerva`, which contains the following commands:\n\n- `create` - creates a project with a standard template;\n- `build` - builds the project according to the current parameters set in the appsetting.json file of the project;\n- `clear` - clears the project build;\n- `run` - starts the project, if there is no project assembly, then pre-starts the assembly;\n- `debug` - runs the project with `VM` services for debugging, the compilation type of the launched assembly should be `JIT`;\n- `test` - starts the server, runs the tests, after the tests completes the server process;\n- `docker` - generates `Dockerfile`.\n\n## Create project\n\nTo create a project with a standard template, the `CLI` utility `Minerva` contains the `create` command.\n\nThe command contains a mandatory parameter `name`, which specifies the name of the project to be created.\n\nUsing the `directory` parameter you can specify the project creation directory.\n\nUsing the `debug-compile-type` and `release-compile-type` parameters, you can specify compilation types for `debug` and `release` project build types. You can change them at any time in the `appsetting.json' file.\n\nUsing the `docker-compile-type` parameter, you can specify the compilation type for which `Dockerfile` will be generated. You can generate the `Dockerfile` again at any time using the `docker` command.\n\n## Build project\n\nBuilding a project in `Minerva` assumes the presence of two types of project assembly:\n\n- `debug` - for debugging and project development;\n- `release` - for final deployment.\n\nWhen creating a project, an `appsetting.json` file is created in its root. This is the main configuration file of the project.\n\n## Run project\n\nThe project is launched using the `run` command, as well as by running the executable file in the /build/`build type`/bin folder (for `JIT` compilation it is `kernel snapshot`, and for `AOT` it is `exe` file) manually. When developing, it is preferable to run the project using the `run` command, since it also involves the automatic assembly of the project.\n\n## Debug\n\nDebugging is only available using `JIT` compilation. To run the application in debug mode, use the `debug` command.\n\n### VS Code\n\nTo start debugging in the VS Code editor, you must create a `launch.json` file and specify the `program` parameter in the `configuration`. In the `program` parameter, you specify the path to the executable file. For `JIT` compilation, `Minerva` creates a `kernel snapshot`, it is stored at the path build/`build type`/bin/main.dill. Example path: `build/debug/bin/main.dill`.\n\n## Testing\n\nTesting is started using the `test` command. Testing involves starting the server, running all tests, and completing the server process.\n\nWhen building a project, a file `test_app_setting.g.dart` is generated in the `test` folder. It stores the port and address that the server uses during the current build. This is done for convenience, so that when you start tests, you can access the settings relevant to the current project build from the `appsetting.json` file.\n\n## Docker\n\nWhen creating a project using the `create` command, a `Dockerfile` is generated for the given compilation type.\n\nDocker files differ for different types of compilation. There are 2 docker file templates:\n\n- `JIT`;\n- `AOT`.\n\nYou can re-generate the docker file with the selected compilation type at any time using the `docker` command. The compilation type is set by the `compile-type` parameter, by default it is `AOT`.\n\nWhen generating the docker file, the assets added to the final build of the project from the `appsetting.json` file are also taken into account. Therefore, after adding them, you must either generate the `Dockerfile` again, or edit the `Dockerfile` manually.\n\n# Project configuration\n\nEach `Minerva` project contains a configuration file `appsetting.json`. This file contains the settings for the `debug` and `release` assemblies of the project, and also allows you to embed values and arbitrary files into the final assembly of the project.\n\nThe `appsetting.json` file contains the `debug` and `release` fields, which contain configuration details of the corresponding project assemblies.\n\nIn them you can configure:\n\n- `host` - the address where the server is started. By default, for `debug` it is `127.0.0.1`, and for `release` it is `0.0.0.0`;\n- `port` - the port on which the server is started. By default, for `debug` it is `5000`, and for `release` it is `8080`;\n- `compile-type` - the type of compilation of the project. Can be either `JIT` or `AOT`;\n- `values` - values embedded in the assembly. You can access them using [configuration manager](#configuration-manager);\n- `logging` - logging configuration. You can read more about it [here](#logging-configuration).\n\nAlso, `appsetting.json` can contain general settings for all types of project builds:\n\n- `values` - values embedded in the assembly. You can access them using [configuration manager](#configuration-manager);\n- `assets` - by specifying assets, you can embed arbitrary files into the project assembly. The path to them is set relative to the project folder.\n\nExample of specifying a list of assets:\n\n```dart\n\"aseets\": [\n  \"/wwwroot\",\n  \"some_file.txt\"\n]\n```\n\n# Framework structure\n\nThe `Minerva` structure was created taking into account the possibility of processing requests in many isolates, to improve performance. Multithreaded request processing is implemented by running a server instance in multiple isolates, using the `shared` parameter of class `HttpServer` from `dart:io` library.\n\nThe server structure can be represented as follows:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/GlebBatykov/minerva/main/doc/images/server_structure.png\" width=\"75%\"/\u003e\n\u003c/div\u003e\n\nUsing the `instance` parameter of the `MinervaSetting` class, you can set the number of isolates in which server instances will be started.\n\nIsolates used by the server are divided into 2 types:\n\n- `server instances`;\n- `agents`.\n\nIf everything is clear with server instances, these are isolates where the server instance is launched, then what are agents?\n\nRunning server instances in different isolates imposes some limitations and inconveniences. For example, you have a database connection, but if we run the server in several isolates, then each of the running server instances must have its own connection. This may not always be convenient, there may be different scenarios for using something like this. Or, for example, we should have some kind of common state between all `server instances`. That's why there are agents in `Minerva`.\n\n`Agent` is a separate isolate that can store a certain state, receives messages with certain actions that it can perform.\n\nAll server instances can access and interact with one `agent`.\n\nThen the scheme of the server operation can be represented as follows:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/GlebBatykov/minerva/main/doc/images/server_structure_with_agent.png\" width=\"75%\"/\u003e\n\u003c/div\u003e\n\nOf course, the fact that the `agent` is executed in a separate isolate hits performance, due to losses in the transmission of messages between isolates. However, it can be a useful tool in specific scenarios.\n\nYou can read more about the agents [here](#agents).\n\n## Components\n\nDuring the server configuration process, you can set [middlewares](#middlewares), [loggers](#logging), [api](#api). All of them are called components.\n\n`Components` are entities that have their own life cycle.\n\nThe components include:\n\n- `middlewares`;\n- `loggers`;\n- `api`.\n\nFrom their life cycle, we can distinguish the presence of the `initialize` method, which is responsible for deferred initialization. During the server configuration process, you create component instances and pass them to Minerva. However, in fact, server instances work in other isolates, and Send/Receive ports impose restrictions on the transfer of certain types of data between isolates. Therefore, there is an `initialize` method in the `components`, which is triggered after transferring them to specific isolates where they will be used.\n\nDeferred initialization can be used to open a connection to some external source. For example, it can be a custom logger that sends logs to another server and it must initialize the connection when the server starts.\n\n# Routing\n\nIn Minerva, request routing is based on pipelined request processing. When configuring the server, middlewares are set that participate in the processing of the received request. You can read more about middlewares [here](#middlewares).\n\nRequest routing in `Minerva` can be represented as:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/GlebBatykov/minerva/main/doc/images/routing.png\" width=\"75%\"/\u003e\n\u003c/div\u003e\n\n## Request processing pipeline\n\nThe request processing pipeline consists of middlewares. In this section, only some of them will be given, about the rest, as well as about the way to create your own middlewares, you can read [here](#middlewares).\n\nThe operation scheme of the request processing pipeline can be represented as follows:\n\n\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/GlebBatykov/minerva/main/doc/images/middlewares_pipeline.png\" width=\"75%\"/\u003e\n\u003c/div\u003e\n\n## Endpoints\n\nEndpoints are used to process requests. Each endpoint has its own address. Endpoint processing returns `dynamic`.\n\nEndpoints are configured using classes derived from the MinervaEndpointsBuilder class.\n\nThere are several scenarios of how `Minerva` interprets the endpoint result before sending a response:\n\n- if you returned `Map\u003cString, dynamic\u003e`, then `Minerva` will send a response with the code `200` and interpret your response as `json`;\n- if you have returned an instance of the `Result` class, then `Minerva` will send a response with the settings specified in the `Result` instance;\n- if you return any other type, then `Minerva` will bring it to a string (using the `toString` method) and send a response with the code `200`.\n\n`Result` is an auxiliary class for configuring the response code, its headers and body. `Minerva` contains ready-made result templates:\n\n- `OkResult` - sends a response with the code `200`;\n- `BadRequestResult` - sends a response with the code `400`;\n- `UnauthorizedResult` - sends a response with the code `401`;\n- `InternalServerErrorResult` - sends a response with the code `500`;\n- `JsonResult` - implies sending `json`, by default has the status code `200`;\n- `NotFoundResult` - sends a response with the code `404`;\n- `FileResult` - implies sending a file for download;\n- `FilePathResult` - implies sending a file for download, allows you to specify the path to the file. The path to the file can be set both absolutely and relative to the project folder;\n- `FileContentResult` - implies sending the contents of the file;\n- `FilePathContentResult` - implies sending the contents of the file. The path to the file can be set both absolutely and relative to the project folder;\n- `RedirectionResult` - sends a response with the code `301` and a resource to redirect the request to.\n\nExample of using the MinervaEndpointsBuilder class to configure endpoints:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get('/hello', (context, request) {\n      return 'Hello, world!';\n    });\n  }\n}\n```\n\nOne of the middlewares supplied with `Minerva` is `EndpointMiddleware`, this handler should be the last in the pipeline. It is he who is responsible for matching the incoming request with the specified endpoints.\n\n### Api\n\nConfiguring endpoints individually can be inconvenient if the endpoints need to have some common context. As a general context, there may be dependencies that you have set using dependency injection.\n\nAn `Api` is a collection of endpoints, with some common context. Their configuration is carried out using classes derived from the MinervaApisBuilder class.\n\nExample of creating a HelloApi, using the MinervaApisBuilder class to configure the api:\n\n```dart\nclass HelloApi extends Api {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get('/hello', _hello);\n  }\n\n  dynamic _hello(ServerContext context, MinervaRequest request) {\n    return 'Hello, world!';\n  }\n}\n\nclass ApisBuilder extends MinervaApisBuilder {\n  @override\n  List\u003cApi\u003e build() {\n    final apis = \u003cApi\u003e[];\n\n    apis.add(HelloApi());\n\n    return apis;\n  }\n}\n```\n\n### Controllers\n\nControllers are the preferred way to configure the server. You can find out more about them [here](https://pub.dev/packages/minerva_controller_generator).\n\n### Request body\n\nYou can access the request body through the `body` field of an instance of the `MinervaRequest` class. The `body` field provides access to an instance of the `RequestBody` class.\n\nInitially, it is represented as bytes (the `data` field), but there are methods for trying to represent it as:\n\n- `text`. To do this, use the `asText` method;\n- `json`. The `asJson` method is used for this;\n- `form`. To do this, use the `asForm` method.\n\n#### FormData\n\nThe `asForm` method of an instance of the `RequestBody` class returns `Future`, which will return an instance of the `FormData` class.\n\nAn instance of the `FormData` class contains the `data` field. This is `Map\u003cString, FormDataValue\u003e` where `String` is the name of the form field, and `FormDataValue` can be of 2 types:\n\n- `FormDataString` is a form field representing a value in the form of a string;\n- `FormDataFile` is a form field containing a file.\n\n### Path parameters\n\nThe path to the endpoint in `Minerva` can contain parameters of the form: `/user/:id`. During the search for the endpoint for the received path, the matching of the path path to the specified parameter template is performed.\n\nYou can access the values of the path parameters using the `pathParameters` field of an instance of the `MinervaRequest` class.\n\nThe path parameter template can also contain its type, the type can be `int` or `double`. Example of a path parameter task with type indication: `/user/int:id`.\n\nThe path parameter template can also contain a regular expression. Example of a path parameter task specifying a regular expression: `/user/:id([0-9])`.\n\nExample of creating an endpoint with path parameters, using path parameters:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get('/user/int:id', (context, request) {\n      final id = request.pathParameters['id'];\n\n      return 'User with id: $id.';\n    });\n  }\n}\n```\n\n### Request filter\n\nIn `Minerva` you can set a query filter that will check the `content-type`, the presence of path parameters/fields in json/fields in form, as well as their compliance with certain data types. If the request does not match the filter, then you will not get an error of data non-compliance, `Minerva` will assume that the endpoint is not suitable for processing the request, if there are no endpoints capable of processing requests, you will get an error `404`.\n\nThe filter is set using the optional `filter` parameter when creating an endpoint.\n\nAvailable filtering types:\n\n- checking the `content-type` header;\n- checking query parameters, their names, and data types;\n- checking the request body for whether it is json, checking the names of json fields, as well as data types;\n- checking the request body for whether it is a form, checking the names of form fields, as well as data types.\n\nExample of using a query filter, filtering queries by the presence of a query parameter and matching its data type int:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get(\n      '/user',\n      (context, request) {\n        final id = request.uri.queryParameters['id'] as int;\n\n        return 'User with id: $id.';\n      },\n      filter: RequestFilter(\n        queryParameters: QueryParametersFilter(\n          parameters: [\n            QueryParameter(\n              name: 'id',\n              type: QueryParameterType.int,\n            )\n          ],\n        ),\n      ),\n    );\n  }\n}\n```\n\n### Websockets\n\nIn `Minerva` you can create endpoints for processing websocket connections.\n\nExample of creating an endpoint for processing a web socket connection:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.ws('/hello', (context, socket) async {\n      socket.add('Hello, world!');\n\n      await socket.close();\n    });\n  }\n}\n```\n\n# Authentication\n\n`Minerva` contains auxiliary tools for working with JWT authentication, as well as authentication by session cookies.\n\n## JWT\n\nWhen creating an endpoint in `Minerva` using the optional `authOptions` parameter, you can set the `JWT` settings for this endpoint.\n\nThe `JWT` settings are set using the `JwtAuthOptions` class, the very existence of an instance of the `JwtAuthOptions` class will assume that the user has successfully completed authentication.\n\nAlso in the `JwtAuthOptions` class you can set:\n\n- `roles`. Roles, one of which must correspond to the user role;\n- `permissionLevel`. Roles can contain a `permissionLevel`, this is the access level. Allows you to configure access to endpoints more flexibly.\n\nExample of creating an endpoint with the specified JWT authentication settings:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get(\n      '/user/:id',\n      (context, request) {\n        final id = request.pathParameters['id'];\n\n        return 'User with id: $id.';\n      },\n      authOptions: AuthOptions(\n        jwt: JwtAuthOptions(\n          roles: ['User'],\n        ),\n      ),\n    );\n  }\n}\n```\n\n`Minerva` contains a ready-made middleware for `JWT` authorization - `JwtAuthMiddleware`. In the pipeline of intermediate request handlers, it should go earlier than `EndpointMiddleware`.\n\nWhen creating an instance of `JwtAuthMiddleware`, you must set the required parameter `tokenVerify`. This is a handler where you prescribe the logic of checking the token for validity and must return `true` if the user is authorized, `false` if the user is not authorized.\n\n`Minerva` does not contain built-in tools for working with `JWT` tokens due to the availability of ready-made third-party packages for this.\n\nWhen creating an instance of `JwtAuthMiddleware`, you can set the `getRole` parameter. This is a handler where you prescribe the logic of getting a role from a token. The handler should return an instance of the `Role` class, specifying the role name. You can also set the `permissionLevel` for the role in.\n\nExample of adding `EndpointMiddleware` to the request processing pipeline:\n\n```dart\nclass MiddlewaresBuilder extends MinervaMiddlewaresBuilder {\n  @override\n  List\u003cMiddleware\u003e build() {\n    final middlewares = \u003cMiddleware\u003e[];\n\n    middlewares.add(ErrorMiddleware());\n\n    // Adds middleware for working with JWT.\n    middlewares\n        .add(JwtAuthMiddleware(tokenVerify: tokenVerify, getRole: getRole));\n\n    middlewares.add(EndpointMiddleware());\n\n    return middlewares;\n  }\n\n  bool tokenVerify(ServerContext context, String token) {\n    // Check token and verify.\n\n    return true;\n  }\n\n  Role getRole(ServerContext context, String token) {\n    // Get info from token.\n\n    return Role('User', permissionLevel: 1);\n  }\n}\n```\n\n## Cookie\n\nWhen creating an endpoint in `Minerva` using the optional parameter `authOptions`, you can set cookie authorization settings for this endpoint.\n\nCookie authorization settings are set using the `CookieAuthOptions` class, the very existence of an instance of the `CookieAuthOptions` class will assume that the user has successfully passed authentication.\n\nExample of creating an endpoint with the specified cookie authorization settings:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get(\n      '/user/:id',\n      (context, request) {\n        final id = request.pathParameters['id'];\n\n        return 'User with id: $id.';\n      },\n      authOptions: AuthOptions(\n        cookie: CookieAuthOptions(),\n      ),\n    );\n  }\n}\n```\n\n`Minerva` contains a ready-made middleware for authorization by cookies - `CookieAuthMiddleware`. In the pipeline of intermediate request handlers, it should go earlier than `Endpoint Middleware`.\n\nWhen creating an instance of `CookieAuthMiddleware`, you must set the mandatory parameter `isAuthorized`. This is a handler where you prescribe the logic for checking cookies and should return `true` if the user is authorized, `false` if the user is not authorized.\n\nExample of adding `CookieAuthMiddleware` to the request processing pipeline:\n\n```dart\nclass MiddlewaresBuilder extends MinervaMiddlewaresBuilder {\n  @override\n  List\u003cMiddleware\u003e build() {\n    final middlewares = \u003cMiddleware\u003e[];\n\n    middlewares.add(ErrorMiddleware());\n\n    middlewares\n        .add(CookieAuthMiddleware(isAuthorized: isAuthorized));\n\n    middlewares.add(EndpointMiddleware());\n\n    return middlewares;\n  }\n\n  bool isAuthorized(ServerContext context, List\u003cCookie\u003e cookie) {\n    // Check cookies\n\n    return true;\n  }\n}\n```\n\n# Middlewares\n\n`Minerva` processes incoming requests using a pipeline of middlewares.\n\n## Ready-made middlewares\n\n`Minerva` contains a number of ready-made middlewares:\n\n- `CookieAuthMiddleware` - can be used to verify authorization by cookies;\n- `JwtAuthMiddleware` - can be used to verify JWT authorization;\n- `ErrorMiddleware` - used to handle errors that occurred in subsequent middlewares in the pipeline;\n- `EndpointMiddleware` - used to match an incoming request with the endpoints specified in `Minerva`. Must be the last in the pipeline;\n- `StaticFilesMiddleware` - can be used to organize the distribution of static files;\n- `RedirectionMiddleware` - can be used to organize redirects, taking into account the availability of appropriate access rights. Using this handler, for example, you can implement a gateway microservice.\n\n## Custom middlewares\n\nYou can create your own middlewares.\n\nEach middleware inherits from the `Middleware` class. When creating a derivative of the `Middleware` class, you need to implement the `handle` method in the derived class. In this method, an instance of the `MiddlewareContext` class is available to you, with its help you can access endpoints, the server context, as well as an incoming request. Each middleware must either process the request independently, returning some result, or delegate this responsibility to the next middleware.\n\nExample of creating your own middleware:\n\n```dart\nclass TestMiddleware extends Middleware {\n  @override\n  dynamic handle(MiddlewareContext context, MiddlewarePipelineNode? next) {\n    print('Hello, middleware world!');\n\n    if (next != null) {\n      return next.handle(context);\n    } else {\n      return NotFoundResult();\n    }\n  }\n}\n```\n\nThe middleware created in the example will print the message `'Hello, middleware world!'`, and also check whether the next middleware exists in the pipeline. If it exists, it delegates the processing of the request to it, and if it is missing, it will return the error `404`.\n\n# Files\n\n## Uploading files\n\nYou can upload a file to the server at by sending the file to `FormData`. Further, when processing a request, you can use the acform method of an instance of the `RequestBody` class to get `FormData` data from an incoming request. Next, you can use the name of the field in Form Data to get an instance of the `FormDataFile` class. An instance of the `FormDataFile` class contains the file name, as well as the contents of the file in the form of bytes.\n\nExample of getting a file using `FormData`, writing a file to an arbitrary path using the `dart:io` library:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.post('/uploadFile', (context, request) async {\n      final formData = await request.body.asForm();\n\n      final fileField = formData['file'] as FormDataFile;\n\n      final file = File.fromUri(Uri.file('somePath'));\n\n      await file.create();\n\n      await file.writeAsBytes(fileField.bytes);\n    });\n  }\n}\n```\n\n## Downloading files\n\nYou can download the file either by configuring the response headers yourself, as well as by transferring the contents of the file to the response using the `Result` class. Or use ready-made results, such as: `FileResult`, `FilePathResult`.\n\nTo send a file using `FileResult`, you must pass an instance of the `File` class from the `dart:io` library to it.\n\nTo send a file using `FilePathResult`, you must specify the path to the file. The path to the file can be set either absolutely or relative to the project folder.\n\nExample of using `FileResult` to return to file downloads:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.post('/downloadFile', (context, request) async {\n      final json = await request.body.asJson();\n\n      final path = json['filePath'];\n\n      final file = File.fromUri(Uri.parse(path));\n\n      if (await file.exists()) {\n        return FileResult(file);\n      } else {\n        return NotFoundResult();\n      }\n    });\n  }\n}\n```\n\n## Static files\n\n`Minerva` contains an middleware for organizing access to static files - `StaticFilesMiddleware`.\n\nWhen creating `StaticFilesMiddleware`, you can set the following settings:\n\n- `directory`. Required parameter. The path to the folder is set relative to the project folder;\n- `path`. Required parameter. The path by which the middleware will try to match the request with static files;\n- `root`. Optional parameter. Specifies the path to the file relative to the specified `directory`, which will be given if the request path matches `path`, will not contain any further path pointing to the required file.\n\nLet's analyze an example of creating an middleware for organizing access to static files.\n\nContents of `MiddlewaresBuilder`:\n\n```dart\nclass MiddlewaresBuilder extends MinervaMiddlewaresBuilder {\n  @override\n  List\u003cMiddleware\u003e build() {\n    final middlewares = \u003cMiddleware\u003e[];\n\n    middlewares.add(ErrorMiddleware());\n\n    middlewares.add(StaticFilesMiddleware(\n      directory: '/wwwroot',\n      path: '/wwwroot',\n      root: 'index.html',\n    ));\n\n    middlewares.add(EndpointMiddleware());\n\n    return middlewares;\n  }\n}\n```\n\nContents of the `appsetting.json` file:\n\n```dart\n{\n    \"debug\": {\n        \"compile-type\": \"JIT\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 5000\n    },\n    \"release\": {\n        \"compile-type\": \"AOT\",\n        \"host\": \"0.0.0.0\",\n        \"port\": 8080\n    },\n    \"assets\": [\n        \"/wwwroot\"\n    ]\n}\n```\n\nStructure of the `wwwroot` folder:\n\n- `images`;\n  - `cat.jpg`;\n- `index.html`;\n- `some_file.txt`;\n\nIn the example given, by assembling the project in the `debug` assembly, we can access the file `cat.jpg`on the way `http://127.0.0.1:5000/wwwroot/images/cat.jpg`.\n\n# Dependency injection\n\n`Minerva` uses classes derived from the `MinervaServerBuilder` class to inject dependencies into each server instance.\n\nOf the built-in tools for implementing dependencies, `Minerva` contains only `ServerStore`. This is a `key-value` collection that you can access using a `ServerContext` instance.\n\nIn the `Dart` ecosystem, there are good packages for implementing dependencies that you can use (for example, [get_it](https://pub.dev/packages/get_it)), having prescribed the logic of dependency injection in a class derived from the `MinervaServerBuilder` class.\n\n# Agents\n\nIn `Minerva`, multithreading of query execution is achieved by deploying a server instance in separate isolates.\n\nAgents are used to organize access to shared data from different server instances. These are entities executed in separate isolates, can have their own state, accept requests. It should be borne in mind that agents are deployed in separate isolates and when accessing them, we get performance losses on the forwarding of messages between isolates. Agents should be used only when the specifics of the logic we implement require a certain common state between different server instances.\n\nEach agent has its own unique name configured at server startup, by which you can later get a connector to this agent.\n\n## Custom agents\n\nAll agent classes in `Minerva` are derived from the `Agent` class.\n\nAgents have methods:\n\n- `initialize`. The method is triggered at the initial initialization of the server. You should use it to open connections, initialize resources;\n- `call`. The method handles call calls to the agent. A call request means that the agent must send a response to the sender;\n- `cast`. The method handles cast calls to the agent. A cast request means that the agent does not send a response to the sender, and the sender does not wait for the results of the cast request;\n- `dispose`. The method is triggered when the agent is destroyed.\n\nAs you may have noticed , there are 2 types of calls to the agent:\n\n- `call`. With the answer;\n- `cast`. No response.\n\nEvery time we contact the agent, we send him an `action`, according to which he already performs some action, and we can also send some data.\n\nLet's consider a simple example of creating agent that stores the state of the counter, as well as creating endpoints that interact with this state.\n\nCreating Agent class:\n\n```dart\nclass CounterAgent extends Agent {\n  int _counter = 0;\n\n  @override\n  dynamic call(String action, Map\u003cString, dynamic\u003e data) {\n    switch (action) {\n      case ('get'):\n        return _counter;\n    }\n  }\n\n  @override\n  void cast(String action, Map\u003cString, dynamic\u003e data) {\n    switch (action) {\n      case ('increment'):\n        _counter++;\n    }\n  }\n}\n```\n\nConfiguring agents, creating agent named `'counter'`:\n\n```dart\nclass AgentsBuilder extends MinervaAgentsBuilder {\n  @override\n  List\u003cAgentData\u003e build() {\n    final agents = \u003cAgentData\u003e[];\n\n    agents.add(AgentData('counter', CounterAgent()));\n\n    return agents;\n  }\n}\n```\n\nCreating Endpoints:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get('/counter/get', (context, request) async {\n      final counter = await context.connectors['counter']!.call('get');\n\n      return 'Counter state: $counter.';\n    });\n\n    endpoints.post('/counter/increment', (context, request) {\n      context.connectors['counter']!.cast('increment');\n    });\n  }\n}\n```\n\nThus, we have created 2 endpoints, and in whichever of the server instances the incoming request is processed, they will interact with the same state of the counter.\n\n# Logging\n\n`Minerva`, as well as creating your own loggers.\n\nLogging divided into the following levels `Minerva` is divided into the following levels:\n\n- `info`;\n- `debug`;\n- `warning`;\n- `error`;\n- `critical`.\n\n## Pipeline\n\nThe logging process in `Minerva` is organized in the form of a pipeline. When configuring the server, you can specify several loggers who will participate in the logging process.\n\n## Ready-made loggers\n\n`Minerva` contains ready-made loggers:\n\n- `ConsoleLogger`. Logs to the console;\n- `FileLogger`. Logs to a file.\n\n### Logging templates\n\nReady-made loggers in `Minerva` allow you to set a logging template.\n\nBy default, the logging template looks like: `'[\u0026time] [\u0026level] \u0026message'`.\n\nThe logging template contains substituted values, such as:\n\n- `\u0026time` - logging time, without date. Can be set as `\u0026time(pattern)`, where `pattern` is a template from the package [intl](https://pub.dev/packages/intl);\n- `\u0026date` - logging date, without time. Can be set as `\u0026date(pattern)`, where `pattern` is a template from the package [intl](https://pub.dev/packages/intl);\n- `\u0026level` - logging level;\n- `\u0026message` - message.\n\n### Logging to files\n\nFor logging to the file `Minerva` contains a ready-made logger `FileLogger`.\n\nThis logger works in conjunction with a ready-made agent `FileLoggerAgent`. You can set the path to the logging file when configuring agents using the `log Path` parameter of the `FileLoggerAgentData` class. The path to the logging file can be set either absolute or relative to the project folder. The relative path must start with `~/`. The default path to the logging file is `~/log/log.log`.\n\nIn order to use a logger to a file, we also need to use `FileLoggerAgent`.\n\nConsider an example of configuring loggers, as well as agents to use `FileLogger`.\n\nConfiguration of loggers:\n\n```dart\nclass LoggersBuilder extends MinervaLoggersBuilder {\n  @override\n  List\u003cLogger\u003e build() {\n    final loggers = \u003cLogger\u003e[];\n\n    loggers.add(FileLogger());\n\n    return loggers;\n  }\n}\n```\n\nAgents Configuration:\n\n```dart\nclass AgentsBuilder extends MinervaAgentsBuilder {\n  @override\n  List\u003cAgentData\u003e build() {\n    final agents = \u003cAgentData\u003e[];\n\n    agents.add(FileLoggerAgentData());\n\n    return agents;\n  }\n}\n```\n\n## Logging configuration\n\nIn `Minerva` when configuring the project build, using `appsetting.json` you can configure logging. You can specify active logging levels for each specific logger, for each type of assembly.\n\nEvery logger in `Minerva` has his own name. For ready-made loggers , this is:\n\n- `ConsoleLogger` - `console`;\n- `FileLogger` - `file`.\n\nIf the settings for any logger are not specified in `appsetting.json`, this means that all logging levels are available for him.\n\nConsider an example where we disable in the `release` build of the project only the `critical`, `error` levels for the `console` logger.\n\nContents of the `appsetting.json` file:\n\n```dart\n{\n    \"debug\": {\n        \"compile-type\": \"JIT\",\n        \"host\": \"127.0.0.1\",\n        \"port\": 5000\n    },\n    \"release\": {\n        \"compile-type\": \"AOT\",\n        \"host\": \"0.0.0.0\",\n        \"port\": 8080,\n        \"logging\": {\n            \"console\": [\n                \"error\",\n                \"critical\"\n            ]\n        }\n    }\n}\n```\n\n## Custom loggers\n\nIn `Minerva` you can create your own loggers, the base class `Logger` is used for this.\n\nEvery logger should implement the methods:\n\n- `info`;\n- `debug`;\n- `warning`;\n- `error`;\n- `critical`.\n\nThese methods are used to log messages of the same logging levels.\n\nThe logger, like any other component, can have the `initialize` method to perform deferred initialization.\n\nThe base class `Logger` implements the `isLevelEnabled` method, with which you can check whether a certain logging level is enabled in the `appsetting.json` file for the current logger.\n\nExample of creating a custom logger named `'custom'`:\n\n```dart\nclass CustomLogger extends Logger {\n  CustomLogger() : super(name: 'custom');\n\n  @override\n  void critical(object) {\n    if (isLevelEnabled(LogLevel.critical)) {\n      print('Level: critical, message: $object.');\n    }\n  }\n\n  @override\n  void debug(object) {\n    if (isLevelEnabled(LogLevel.debug)) {\n      print('Level: debug, message: $object.');\n    }\n  }\n\n  @override\n  void error(object) {\n    if (isLevelEnabled(LogLevel.error)) {\n      print('Level: error, message: $object.');\n    }\n  }\n\n  @override\n  void info(object) {\n    if (isLevelEnabled(LogLevel.info)) {\n      print('Level: info, message: $object.');\n    }\n  }\n\n  @override\n  void warning(object) {\n    if (isLevelEnabled(LogLevel.warning)) {\n      print('Level: warning, message: $object.');\n    }\n  }\n}\n```\n\n# Configuration manager\n\nThe values that you set using the `values` parameter in `appsetting.json` are available to you while the server is running, you can access and modify them. When building and starting the server, you interact with the `appsetting.json` of the current build, not the entire project. The modification of the value also occurs within the current build, and not the entire project.\n\nTo work with the `values` of the current build, the `ConfigurationManager` class is available in `Minerva`. To load values from `appsetting.json` into the current instance of `ConfigurationManager`, the `load` method is available to you. Next, you can modify these values within the current instance of `ConfigurationManager`, and also save them to the `appsetting.json` file using the `save` method.\n\nExample of using the `ConfigurationManager` class:\n\n```dart\nclass EndpointsBuilder extends MinervaEndpointsBuilder {\n  @override\n  void build(Endpoints endpoints) {\n    endpoints.get('/hello', (context, request) async {\n      final configuration = ConfigurationManager();\n\n      await configuration.load();\n\n      return configuration['message'];\n    });\n  }\n}\n```\n\n# Deployment\n\nMinerva contains 2 types of project builds: `debug` and `release`.\n\nYou can build a project in `release` build using the command:\n\n```dart\nminerva build -m release\n```\n\nThe project will be built according to the settings specified in the `appsetting.json` file in the project root.\n\nAfter the build in your project folder, in the `build` folder you will see the `release` folder. Next, you can use this folder and its contents to deploy the server.\n\nYou can delete the `details.json` file  in the project's build folder, since it is useful only when developing a project, when reassembling a project.\n\n## Docker container\n\nWhen you create a Minerva project, a `Dockerfile` is created in the root of your project to build a `Docker image`.\n\nThere are two templates of `Docker` files in Minerva: `AOT` and `JIT`. Templates for the corresponding compilation types, since their deployment in the `Docker container` is different.\n\nBy default, Minerva creates a `Dockerfile` for `AOT` compilation, however, you can specify the required type of compilation `Dockerfile` when creating a project.\n\nDuring development, you can add arbitrary files to the build of your project using the `assets` parameter in the `appsetting.json` file.\n\nIf you added any files to the `asset` folder (which is created in the template `Minerva` project), or added any other assets to the `appsetting.json` file, then before creating the `Docker image` you must - regenerate the `Dockerfile`.\n\nYou can regenerate the `Dockerfile` using one command - the `docker` command in the `CLI` utility `Minerva` of the framework.\n\nThis command will regenerate the `Dockerfile` and add the lines necessary to add all your assets to the final `Docker image`.\n\nBy default, the command generates a `Dockerfile` for `AOT` compilation of the project, but you can specify the type of compilation you need.\n\n`Dockerfile` generation command for `AOT` compilation:\n\n```dart\nminerva docker -c AOT\n```\n\n`Dockerfile` generation command for `JIT` compilation:\n\n```dart\nminerva docker -c JIT\n```\n\nNext, after successfully creating a `Docker image`, you can safely deploy a `Docker container` with your application.\n\n# Road map\n\n- ✅ Finish error handling;\n- ✅ Make documentation;\n- ✅ Make more examples;\n- 🚧 Speed up JSON serialization and deserialization;\n- 🚧 Remove errors when working with multipart/form-data and speed up multipart/form-data parsing time;\n- 🚧 Cover with tests;\n- 🔜 Add Swagger, OpenAPI specification generation;\n- 🔜 Create training videos;\n- 🔜 Create website with documentation;\n- 🔜 Add performance tests.\n\nAnd of course the correction of errors that will be detected.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglebbatykov%2Fminerva","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fglebbatykov%2Fminerva","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fglebbatykov%2Fminerva/lists"}