|
| 1 | +# Poly Typescript Backend Testing |
| 2 | + |
| 3 | +The goal of this project is to test the limits of the typescript backend frameworks. The project will be a simple CRUD API with a single entity. Each framework will be tested for the following: |
| 4 | +- Type safety |
| 5 | +- Community support |
| 6 | +- Developer support |
| 7 | +- Developer experience |
| 8 | +- Documentation |
| 9 | +- Performance |
| 10 | + |
| 11 | +The type safe is a major factor in the evaluation of the frameworks. Most of our projects in Poly were written in typescript, and while we do like the language, we have had some issues with type safety on several backend frameworks. We hope to find a framework that is both completely type safe and has a good developer experience. |
| 12 | + |
| 13 | +## Tested libraries |
| 14 | + |
| 15 | +- [x] [hono](https://www.npmjs.com/package/hono) |
| 16 | +- [x] [elysia](https://www.npmjs.com/package/elysia) |
| 17 | +- [x] [express-zod-api](https://www.npmjs.com/package/express-zod-api) |
| 18 | +- [x] [encore.ts](https://www.npmjs.com/package/encore.ts) |
| 19 | +- [x] [fastify](https://www.fastify.io/) |
| 20 | +- [x] [tsed](https://tsed.dev/) |
| 21 | +- [x] [adonisJs](https://adonisjs.com/) |
| 22 | +- [x] [nestjs](https://nestjs.com/) |
| 23 | +- [ ] [orpc](https://github.com/unnoq/orpc) |
| 24 | +- [ ] [koa](https://www.npmjs.com/package/koa) |
| 25 | +- [ ] [loopback](https://loopback.io/doc/en/lb4) |
| 26 | +- [ ] [sails](https://sailsjs.com/) |
| 27 | + |
| 28 | + |
| 29 | +### Test Plan |
| 30 | + |
| 31 | +Each project will try to have theses features: |
| 32 | + |
| 33 | +- An auth middleware that will add a user to the request / context so that routes can access it (should be typed) |
| 34 | +- Route `authors/:id` with a `PUT` method that takes a body and a param and will return an author with with combination of the body and the param and the user from the auth middleware (everything should be typed) |
| 35 | +- A properly constructed openapi file |
| 36 | + - The open api file should be easily configurable |
| 37 | + - The type between the openapi file and the route should be checked |
| 38 | +- A `/reference` route that will serve a scalar ui website thanks to the openapi file |
| 39 | + |
| 40 | + |
| 41 | +## Development feedback |
| 42 | + |
| 43 | +### Hono |
| 44 | + |
| 45 | +I need to use a third party libraries for validation, a complete type safety: |
| 46 | + |
| 47 | +- :no_entry: The [hono-openapi](https://hono.dev/examples/hono-openapi) does not type check the result types and the schema given in the openapi file. |
| 48 | + |
| 49 | +- :white_check_mark: The [Zod OpenAPI](https://hono.dev/examples/zod-openapi) generates the openapi file from the zod schema and the response type is checked between the route and the schema which is a good thing, but the library is a bit verbose. |
| 50 | + |
| 51 | + |
| 52 | +### Elysia |
| 53 | + |
| 54 | +Easy to setup / use and type safe everywhere. |
| 55 | + |
| 56 | +It looks like scalar may have a problems with 'examples' in params. |
| 57 | + |
| 58 | +example of openapi file given by Elysia: |
| 59 | +```JSON |
| 60 | +"parameters": [ |
| 61 | + { |
| 62 | + "description": "The ID of the author", |
| 63 | + "examples": [ |
| 64 | + "12" |
| 65 | + ], |
| 66 | + "schema": { |
| 67 | + "type": "string", |
| 68 | + "minLength": 1 |
| 69 | + }, |
| 70 | + "in": "path", |
| 71 | + "name": "id", |
| 72 | + "required": true |
| 73 | + } |
| 74 | +], |
| 75 | +``` |
| 76 | + |
| 77 | +example of what is given by Hono |
| 78 | +```JSON |
| 79 | +"parameters": [ |
| 80 | + { |
| 81 | + "schema": { |
| 82 | + "type": "string", |
| 83 | + "minLength": 3, |
| 84 | + "description": "The ID of the user", |
| 85 | + "example": "1212121" |
| 86 | + }, |
| 87 | + "required": true, |
| 88 | + "name": "id", |
| 89 | + "in": "path" |
| 90 | + } |
| 91 | +], |
| 92 | +``` |
| 93 | + |
| 94 | +Everywhere else the examples field works fine but not in the params, problem with scalar ? |
| 95 | + |
| 96 | +Elysia may have an older version of Typebox as dependence, so if our project wants to use the latest release of Typebox, we will have to create an alias in package.json to use this version. (Also, keep it mind that it wont be compatible with the Elysia controller types) |
| 97 | + |
| 98 | +### express-zod-api |
| 99 | + |
| 100 | +Distinction between the body, the params and body are not clear. |
| 101 | + |
| 102 | +The hot reloading is not working properly ? |
| 103 | + |
| 104 | +Based on express so very slow. |
| 105 | + |
| 106 | +### Encore.ts |
| 107 | + |
| 108 | +As of 24/12/2024, the framework looks a bit incomplete: |
| 109 | +- Cannot add objects to context |
| 110 | +- OpenAPI is still experimental |
| 111 | + |
| 112 | +Look promising. |
| 113 | + |
| 114 | +### Koa |
| 115 | + |
| 116 | +Terrible doc |
| 117 | +Stopped testing after trying to find a library that would properly type the routes, found one with 60 github stars but way less complete than the express zod api one. |
| 118 | +[koa-zod-router](https://github.com/jakefenley/koa-zod-router#readme) |
| 119 | + |
| 120 | + |
| 121 | +### Fastify |
| 122 | + |
| 123 | +Hard time to find how to properly type a decorator. Cannot ? |
| 124 | + |
| 125 | +### Tsed |
| 126 | + |
| 127 | +Easy to use, but the it suffers from the same problem as nestjs, the decorators and the typescript are not typed together. |
| 128 | + |
| 129 | +5 min to shut down the server ? |
| 130 | + |
| 131 | +based on express so very slow. |
| 132 | + |
| 133 | +### AdonisJs |
| 134 | + |
| 135 | +Adding context is not easy and truly typed. |
| 136 | + |
| 137 | +OpenAPI is not easily configurable (BY COMMENT !!) AND not typed safely. |
| 138 | + |
| 139 | +Some requests are failing when benching it. |
| 140 | + |
| 141 | + |
| 142 | +### NestJs |
| 143 | + |
| 144 | +Was really easy to setup and make it work. |
| 145 | + |
| 146 | +Personal preferences but chaining decorators is not easily readable. |
| 147 | + |
| 148 | +@ApiResponse is not typed with the return type of the route. |
| 149 | + |
| 150 | +Doc looks great |
| 151 | + |
| 152 | +You can give custom "context" thanks to the custom param decorator but the type is not well checked. |
| 153 | + |
| 154 | +based on express so very slow. |
| 155 | + |
| 156 | +## Results |
| 157 | + |
| 158 | +| | body typed | param typed | context typed | open api | open api typed end to end | front end client | perf (avg request) | |
| 159 | +|-----------------|--------------------|--------------------|--------------------|--------------------|---------------------------|--------------------|--------------------| |
| 160 | +| hono | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 28.4k | |
| 161 | +| elysia | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 45.3k | |
| 162 | +| express-zod-api | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | :white_check_mark: | 5.2k | |
| 163 | +| encore.ts | :white_check_mark: | :white_check_mark: | :no_entry: | :no_entry: | :no_entry: | :white_check_mark: | 8.5k | |
| 164 | +| fastify | :white_check_mark: | :white_check_mark: | :no_entry: | :white_check_mark: | :white_check_mark: | :no_entry: | 10.3k | |
| 165 | +| tsed | :white_check_mark: | :white_check_mark: | :no_entry: | :white_check_mark: | :no_entry: | :no_entry: | 3.4k | |
| 166 | +| adonisJs | :white_check_mark: | :white_check_mark: | :no_entry: | :white_check_mark: | :no_entry: | :no_entry: | 6k | |
| 167 | +| nestjs | :white_check_mark: | :white_check_mark: | :no_entry: | :white_check_mark: | :no_entry: | :no_entry: | 4.3k | |
| 168 | + |
| 169 | + |
| 170 | +### Remarks |
| 171 | + |
| 172 | +We can see that express based frameworks are slower than the others. |
| 173 | + |
| 174 | +### The express like frameworks |
| 175 | + |
| 176 | + We do have a three frameworks that achieve the type safety we were looking for and are express like (chaining functions, middleware, etc): |
| 177 | + |
| 178 | +- hono |
| 179 | +- elysia |
| 180 | +- express-zod-api |
| 181 | + |
| 182 | +[Hono](https://github.com/honojs/hono) has 20k stars, and it looks like it has a good community / plugins. Also the performance is good for a javascript framework. In order to have the complete type safety, we need to use a third party library [Zod OpenAPI](https://hono.dev/examples/zod-openapi) which is a bit verbose. |
| 183 | + |
| 184 | +[Elysia](https://github.com/elysiajs/elysia) has 11k stars, has a bit less community than hono. It has a very good performance, the type safety is complete, and completely integrated in the framework. It was initially made for Bun but it is now an agnostic framework. |
| 185 | + |
| 186 | +[Express-zod-api](https://github.com/RobinTail/express-zod-api) has 600 stars, has mediocre performance because it is based on express. The type safety is complete. The advantage of this framework is that it is based on express, we can use the insane amount of plugins available for express. |
| 187 | + |
| 188 | +### The more "architecture opinionated" frameworks |
| 189 | + |
| 190 | +We can see that when a framework is more architecture opinionated, the type safety is not complete. The frameworks using decorators like nest.js cannot achieve a complete type safety. |
| 191 | + |
| 192 | +I find [encore.ts](https://github.com/encoredev/encore) a promising framework. It has 8.3k stars, the type safety is not complete yet and some features are missing but it looks like it can provides a good developer experience. |
| 193 | + |
| 194 | + |
| 195 | +### Result are a bit biased |
| 196 | + |
| 197 | +Some of these frameworks are huge, and I did not graded them on all the features they offer. Obviously complete type safety and performance is not the only thing we have to look for when we are choosing a typescript backend server. |
| 198 | + |
| 199 | +Also the bench is done of my computer and is based on a simple route. The performance can be different with a more complex route, or with a database, with more features, etc. |
0 commit comments