{"id":24294386,"url":"https://github.com/osaxma/mid","last_synced_at":"2025-10-23T02:40:29.732Z","repository":{"id":56852811,"uuid":"521673318","full_name":"osaxma/mid","owner":"osaxma","description":"Build an End-to-End Typesafe APIs (Experimental)","archived":false,"fork":false,"pushed_at":"2022-09-19T13:04:40.000Z","size":645,"stargazers_count":16,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-01-16T17:38:25.902Z","etag":null,"topics":["api","backend","dart","flutter"],"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/osaxma.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-08-05T14:42:12.000Z","updated_at":"2024-07-26T00:05:56.000Z","dependencies_parsed_at":"2022-09-04T14:40:27.586Z","dependency_job_id":null,"html_url":"https://github.com/osaxma/mid","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osaxma%2Fmid","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osaxma%2Fmid/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osaxma%2Fmid/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/osaxma%2Fmid/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/osaxma","download_url":"https://codeload.github.com/osaxma/mid/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":234655715,"owners_count":18866986,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api","backend","dart","flutter"],"created_at":"2025-01-16T17:39:01.933Z","updated_at":"2025-09-30T12:30:22.055Z","avatar_url":"https://github.com/osaxma.png","language":"Dart","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mid \n\n\u003e ⚠️ warning: the project is still experimental!\n\u003e\n\u003e so things may change often until a stable version is released.\n\n`mid` is a tool to build an end-to-end typesafe API in dart. The tool generates an API server and a client library in addition to handling requests and managing communication between the server and the client. \n\nIn short:\n\n- You write this on the server side:\n    ```dart\n    class App extends EndPoints {\n        final Database database;\n\n        App(this.database);\n\n        Future\u003cUserData\u003e getUserData(int uid) async {\n            final user = await database.getUserById(uid);\n            return user;\n        }\n\n        Stream\u003cList\u003cPost\u003e\u003e timeline(int uid) {\n            return database.timelineStream(uid);\n        }\n\n        Future\u003cvoid\u003e updateProfile(UserProfile profile) {\n            await database.updateProfile(profile);\n        }\n    }\n    ```\n\n- And `mid` enables you to do this on the client side:\n\n    ```dart\n    final client = TheClient(url: 'localhost:8080');\n\n    final UserData data = await client.getUserData(42); \n\n    final  Stream\u003cList\u003cPost\u003e\u003e posts = await client.timeline(uid); \n\n    final newProfile = profile.copyWith(photoURL: photoURL);\n    await client.updateProfile(newProfile); \n    ```\n\n\nSee the [Quick Start Tutorial](https://github.com/osaxma/mid/tree/main/tutorials/quick_start/README.md) to learn how to use `mid` in no time. \n\n## Getting Started\n\n### Installation:\n\n```sh\ndart pub global activate mid\n```\n\n### Tutorials:\n\n- [Quick Start Tutorial](https://github.com/osaxma/mid/tree/main/tutorials/quick_start/README.md) \n\n\n### Examples:\n\n- Examples will be added **SOON** to the [examples](https://github.com/osaxma/mid/tree/main/examples) folder. \n\n### Documentation\nThe documentation is being created incrementally within [docs](https://github.com/osaxma/mid/tree/main/docs) folder. Currently the following is available:\n\n- [client](https://github.com/osaxma/mid/blob/main/docs/client.md)\n- [server](https://github.com/osaxma/mid/blob/main/docs/server.md)\n- [interceptors](https://github.com/osaxma/mid/blob/main/docs/interceptors.md)\n- [project_structure](https://github.com/osaxma/mid/blob/main/docs/project_structure.md)\n- [serialization](https://github.com/osaxma/mid/blob/main/docs/serialization.md)\n\n## Motivation\n\nTo have the ability to call the backend code from the frontend in a type safe manner and as simple as calling a function in pure Dart. \n\n\u003e Note: `mid` is not intended to generate a REST API, but to generate an API server that can be seamlessly used by a Dart or Flutter frontend with a minimal effort. \n\n\n## How does it work\n`mid` simply works by converting the public methods for a given list of classes into endpoints on a [shelf][] server by generating a [shelf_router][] and its handlers. In addition, the return type and the parameters types of each method are parsed and analyzed to generate the serialization/deserialization code for each type. \n\nThe client library is generated in a similar manner where each class, method, return type and parameter type is regenerated so that each endpoint becomes a simple function. \n\nTo support streaming data from the server to the client, [shelf_web_socket] is used on the server while [web_socket_channel][] on the client. \n\n[shelf]: https://pub.dev/packages/shelf\n[shelf_router]: https://pub.dev/packages/shelf_router\n[shelf_web_socket]: https://pub.dev/packages/shelf_web_socket\n[web_socket_channel]: https://pub.dev/packages/web_socket_channel\n\n## Additional Notes \n\n### Supported Classes\nAny class of an `EndPoints`\\* type. `mid` will only expose the public methods of the given class and it'll not expose any of its superclass(es).\n\n\\* `EndPoints` is just a type -- for now there's nothing to implement. a class just needs to implement, extend or mixin `EndPoints` so it can be converted. \n\n### Supported Return Types and Method Parameters Types \n\n- All core Types (`int`, `double`, `num`, `bool`, `String`, `DateTime`, `Duration`, `enum`, `Uri`, `BigInt`)\n- User defined Classes\\*\n- Collections (i.e., `Map`, `Set`, `List`) of any of the above.\n- `Future` or `Stream` for any of the above. \n\n    \\* `mid` is able to serialize user defined classes and their members recursively as long as they have an unnamed generative constructor with formal parameters only (i.e. all parameters using `this`). An example class would be:\n    \n    ```dart\n    class UserData {\n      final int id;\n      final String name;\n      final bool isAdmin;\n      // `MetaData` must follow the same rules including its members.\n      final MetaData? metadata;\n    \n      // this is what `mid` is looking for (i.e. no assignment in initializer list or constructor body):\n      UserData({\n        required this.id,\n        required this.name,\n        this.metadata,\n        this.isAdmin = false,\n      });\n    \n      /* you can define your own methods, factory constructors, and whatnot */\n    }\n    ```\n\n\n\n\u003c!-- \n\n\n\n```dart\nFuture\u003cList\u003cObject\u003e\u003e endpoints(Logger logger) async {\n    final database = Database(url: String.fromEnvironment('DATABASE_URL'));\n    return [\n       App(apiKey: apiKey, database: database, logger: logger);,\n    ];\n}\n```\n\nOne can also create multiple routes and endpoints such as:\n\n\n```dart\nFuture\u003cList\u003cObject\u003e\u003e endpoints(Logger logger) async {\n    final database = Database(url: String.fromEnvironment('DATABASE_URL'));\n    final storageURL = String.fromEnvironment('STORAGE_KEY');\n    final apiKey = String.fromEnvironment('API_KEY');\n\n    final authAPI =  Auth(database: database, logger: logger);\n\n    final storageAPI = Storage(apiKey: apiKey, url: storageURL, database: database, logger: logger);\n\n    final applicationAPI = App(apiKey: apiKey, database: database, logger: logger);\n\n    return [\n        authAPI,\n        storageAPI,\n        applicationAPI,\n    ];\n}\n```\n\n\n --\u003e\n\n\n\n\u003c!-- \n\n## Roadmap \n\n[ ] API versioning and Preventing Unintended Breaking Changes\n\nDisscusion: the idea here is to track methods return types and parameters so they do not break the api for apps, especially the one running an older version.\nFor instance, adding a new required parameter to a method or changing the name of a parameter can break the api for existing apps. `mid` should keep track of API changes somehow and warn the user when such a change occurs. This could be done by storing the generated APIs in some sort of a database and whenever `mid generate endpoints` is called, `mid` would compare the newly generated API with the previous one and present the user with appropriate warning. \n\n\nif @serverOnly is supported for serializable class members, add the following caveat:\n\nWhen a `Type` is used in a return statement as well as an argument, any member annotated with `@serverOnly` must be optional (i.e. either nullable or with a default value).\n```dart \nFuture\u003cUser\u003e getUserData() {/* */}\nFuture\u003cvoid\u003e updateUserData(User user) {/* */}\n\nclass User {\n    final int id;\n    final String name;\n\n    @serverOnly\n    final bool isBanned; // \u003c~~ must be optional or nullable \n}\n```\n\nThe main reason is that when a client invoke `updateUserData`, it'll be impossible to instantiate `User` without a value for `isBanned` since the data coming from the client wouldn't have a value for it. That's because when `User` is generated for the client, it wouldn't have `isBanned` field due to the `@serverOnly` annotation. \n\nnote: \n    - idea 1: I think it's possible to have a lint rule for that (warning: isBanned must have a default value or be nullable)\n    - idea 2: change `@serverOnly` so that it accepts an argument of `default value`\n\n --\u003e\n\n\n \u003c!-- \n about generated code:\n The code generated by `mid` is intended to be human-readable, tho it's quite redundant. In other words, `mid` does not generate any magic code -- it removes the heavylifting of writing the same code repeatedly in both server and client. \n\n  --\u003e\n\n\n  \u003c!-- \n  caching:\n\n  cache response for functions where input is the same. On the server, the user may add an annotation such as @Cachable(duration: ....) (also added as headers on http request)\n  the args can be hashd as a key for the cache. \n   --\u003e\n\n\n\n\u003c!-- \n\nTo Generate coverage:\n\n- run in root project:\n    dart test --coverage=\"coverage\"  \n- then:\n    format_coverage --lcov --in=coverage --out=coverage/coverage.lcov  --report-on=lib\n- then:\n    genhtml coverage/coverage.lcov -o coverage/html  \n- then open it:\n    open coverage/html/index.html \n--\u003e\n\n\n\n\u003c!-- \n1. **Create a `mid`  project:**\n      ```sh\n      mid create \u003cproject_name\u003e\n      ```\n      This will create two dart projects in the following structure:\n      ```\n      \u003cproject_name\u003e\n            |- \u003cproject_name\u003e_client\n            |- \u003cproject_name\u003e_server\n      ```\n\n  2. **open `\u003cproject_name\u003e_server/mid/endpoints.dart` and add your endpoints there.**\n\n        for example:\n\n        ```dart\n        Future\u003cList\u003cEndPoints\u003e\u003e getEndPoints(Logger logger) async {\n            final database = Database(url: String.fromEnvironment('DATABASE_URL'));\n            final storageURL = String.fromEnvironment('STORAGE_KEY');\n            final apiKey = String.fromEnvironment('API_KEY');\n\n            final authAPI =  Auth(database: database, logger: logger);\n\n            final storageAPI = Storage(apiKey: apiKey, url: storageURL, database: database, logger: logger);\n\n            final applicationAPI = App(apiKey: apiKey, database: database, logger: logger);\n\n            return [\n                authAPI,\n                storageAPI,\n                applicationAPI,\n            ];\n        }\n        ```\n        \n  \n      You can create the endpoints classes inside the `lib` folder, and then import them to the `endpoints` file. \n\n  3. **Generate server and client libraries:**\n\n      ```sh\n      mid generate all \n      ```\n\n  4. **run the server from within `\u003cproject_name\u003e_server` directory**\n\n      ```sh\n      dart run bin/server.dart\n      -\u003e Server listening on port 8000\n      ```\n  5. **import the client project into your frontend and you're set to go**\n    \n        a. Inside the root of the fronted project, run the following:\n\n        ```sh\n        flutter pub add \u003cproject_name\u003e_client --path \"/path/to/\u003cproject_name\u003e_client\"\n        ```\n        b. Inside the file where you'd like to use the client, import the package:\n        ```dart\n        import 'package:\u003cproject_name\u003e_client/\u003cproject_name\u003e_client.dart';\n        ```\n        c. To get the client, it'll be:\n        ```dart\n        // replace `ProjectName` with the actual project name\n        // replace `localhost:8080` with the actual url and port if different. \n        final client = ProjectNameClient(url: 'localhost:8080'); \n        ```\n\n\n --\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosaxma%2Fmid","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fosaxma%2Fmid","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fosaxma%2Fmid/lists"}