├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .nvmrc ├── .sequelizerc ├── @types ├── knex-stringcase.d.ts └── sequelize.d.ts ├── README.md ├── assets ├── db.png ├── npm.png ├── performance-get.png ├── performance-post.png └── performance-simple.png ├── data.json ├── package-lock.json ├── package.json ├── pull_request_template.md ├── server.ts ├── src ├── knex │ ├── config.ts │ ├── db.ts │ ├── index.ts │ ├── interfaces.ts │ └── migrations │ │ └── 20160911120323_create_tables.ts ├── objection │ ├── config.ts │ ├── db.ts │ ├── index.ts │ └── migrations │ │ └── 20160911120323_create_tables.ts ├── sequelize │ ├── config.ts │ ├── index.ts │ └── models │ │ ├── Item.ts │ │ ├── Order.ts │ │ └── index.ts └── typeorm │ ├── config.ts │ ├── db.ts │ ├── entities │ ├── Item.ts │ └── Order.ts │ └── index.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | # /node_modules/* in the project root is ignored by default 2 | # build artefacts 3 | dist/* 4 | coverage/* 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | ecmaVersion: 2019, 5 | sourceType: 'module' 6 | }, 7 | plugins: ['@typescript-eslint/tslint', 'prettier'], 8 | extends: ['plugin:@typescript-eslint/recommended'] 9 | }; 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Frontend Dist Files 2 | app/dist 3 | 4 | # Dist directory 5 | dist 6 | 7 | # Mac Files 8 | .DS_Store 9 | */DS_Store 10 | 11 | # Logs 12 | logs 13 | *.log 14 | npm-debug.log* 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 28 | .grunt 29 | 30 | # node-waf configuration 31 | .lock-wscript 32 | 33 | # Compiled binary addons (http://nodejs.org/api/addons.html) 34 | build/Release 35 | 36 | # Dependency directory 37 | node_modules 38 | 39 | # Optional npm cache directory 40 | .npm 41 | 42 | # Optional REPL history 43 | .node_repl_history 44 | 45 | # Code coverage 46 | ./coverage 47 | 48 | # dotenv 49 | .env 50 | 51 | # VS Code 52 | .vscode/ 53 | 54 | # Webstorm 55 | .idea/ 56 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 10.17.0 -------------------------------------------------------------------------------- /.sequelizerc: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | 'config': path.resolve('dist/src/sequelize', 'config.js'), 5 | 'migrations-path': path.resolve('dist/src/sequelize', 'migrations'), 6 | 'models-path': path.resolve('dist/src/sequelize', 'models') 7 | }; 8 | -------------------------------------------------------------------------------- /@types/knex-stringcase.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'knex-stringcase'; -------------------------------------------------------------------------------- /@types/sequelize.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import * as sequelize from 'sequelize'; 3 | 4 | declare module 'sequelize' { 5 | interface BulkCreateOptions { 6 | include?: Includeable | Includeable[]; 7 | } 8 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ORM research for TypeScript Node.js applications 2 | 3 | ## Table of contents 4 | 5 | * [Introduction](#introduction) 6 | + [Important concepts](#important-concepts) 7 | + [Which libraries are we going to analize?](#which-libraries-are-we-going-to-analize) 8 | * [Case study](#case-study) 9 | + [Database schema](#database-schema) 10 | + [Endpoints](#endpoints) 11 | * [Results](#results) 12 | + [Community](#community) 13 | + [Documentation](#documentation) 14 | + [TypeScript integration](#typescript-integration) 15 | + [Performance](#performance) 16 | * [Developing](#developing) 17 | + [Database configuration](#database-configuration) 18 | + [Migrations](#migrations) 19 | + [Starting app](#starting-app) 20 | + [Debugging](#debugging) 21 | * [License](#license) 22 | 23 | 24 | ## Introduction 25 | 26 | **Object-Relational Mapping (ORM)** is a technique that consists in encapsulate the code needed to manipulate the data in your database, so you don't use SQL anymore; you interact directly with an interface in the same language you're using. 27 | 28 | When people talk about an ORM, they usually make reference to a library that implements this technique. You can find a great explanation in the following link from [StackOverflow](https://stackoverflow.com/questions/1279613/what-is-an-orm-how-does-it-work-and-how-should-i-use-one). 29 | 30 | On the other hand, as it name claims, a **query builder** is an interface that allows you to write SQL in your prefered language. Main difference with ORMs is that you don't have to define models structure for a query builder because you are not working with objects that represents your data. 31 | 32 | This research objective isn't to claim which is the best ORM out there but to provide an objective vision about these libraries most important features, performance, community, documentation, and more. 33 | 34 | ## Case study 35 | 36 | ### Libraries 37 | 38 | - [Sequelize](https://sequelize.org/): It features solid transaction support, relations, eager and lazy loading, read replication and more. It is one of the most complete ORMs for Node.js. It has support for Postgres, MySQL, MariaDB, SQLite and Microsoft SQL Server. 39 | - [Knex](http://knexjs.org/): It's a very powerful query builder with transactions support. It hasn't all features an ORM may has but its performance it's quite better. Postgres, MSSQL, MySQL, MariaDB, SQLite3, Oracle, and Amazon Redshift. 40 | - [TypeORM](https://typeorm.io/#/): Its goal is to always support the latest JavaScript features and provide additional features that help you to develop any kind of application that uses databases. It supports MySQL, MariaDB, Postgres, CockroachDB, SQLite, Microsoft SQL Server, Oracle and [MongoDB NoSQL](https://github.com/typeorm/typeorm/blob/master/docs/active-record-data-mapper.md). 41 | - [Objection](https://vincit.github.io/objection.js/): It's build on Knex, thus supports the same databases. It has all the benefits of an SQL query builder but also a powerful set of tools for working with relations, for this it can be considered an ORM. 42 | 43 | Other libraries were considered for this research but we discarded them, I’m going to do a special mention for the following: 44 | 45 | - [Bookshelf](https://bookshelfjs.org/): Another query builder based on Knex, like Objection. After comparing both, we opted for Objection because we considered that its API implementation is better and a preliminary performance tests showed has the best performance. 46 | - [Waterline](https://waterlinejs.org/): It’s [Sails.js](https://sailsjs.com/) ORM library. Although it seems very interesting, there is a lack of documentation about standalone implementation. Also we couldn’t find any good examples out there about a project implementing it outside Sails environment. 47 | 48 | ### Database schema 49 | 50 | For research purposes, we had to define a database schema. We wanted to keep it simple but also explore models relations API of each library. For that reason we decided to implement a simple ManyToMany relation. We used [Postgres](https://www.postgresql.org/) as database engine. 51 | 52 | Above you can see database schema graph and its definition. 53 | 54 | database schema 55 | 56 | ``` 57 | Table orders { 58 | id int [pk] 59 | user string 60 | date timestamp 61 | } 62 | 63 | Table items { 64 | id int [pk] 65 | name string 66 | value float 67 | } 68 | 69 | Table order_items { 70 | order_id int [ref: > orders.id] 71 | item_id int [ref: > items.id] 72 | } 73 | 74 | ``` 75 | 76 | ### Implementation 77 | 78 | In order to have where implement our study libraries we developed a simple [Express.js](https://expressjs.com/) application with two endpoints. Main idea is to contrast how these libraries behave in most common use cases, check performance, compare implementation complexity, etc. 79 | 80 | ``` 81 | # ⬇️ Get all orders 82 | # 83 | # If query string 'simple' is set to "true", then app will return 84 | # just the records from "orders" table. Otherwise, we are going to 85 | # receive orders array with its nested relations. 86 | GET //orders?simple= 87 | 88 | # ⬆️ Create orders 89 | # 90 | # This endpoint receives an array of orders and create them. 91 | # Orders could include nested relations, in that cases app should 92 | # hanlde it and create them too. 93 | POST //orders # Create many orders with their items 94 | ``` 95 | 96 | The key on this implementation is that we are managing nested resources: an order has one or many items. Payloads we receive has orders and its nested items, and we have to handle them. 97 | 98 | After all this setup we are finally ready to implement our libraries and start having some results. 99 | 100 | ## Results 101 | 102 | ### Community 103 | 104 | First, we can look at NPM packages and their metrics to have a clear image about how much they are being used. 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 117 | 118 | 119 | 120 | 121 | 122 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 141 | 142 | 143 | 144 | 145 |
LibraryCreation dateWeekly downloadsDependents
115 | Sequelize 116 | Jul 22, 2010446,3123,139
123 | Knex 124 | Dec 29, 2012410,0651,953
131 | TypeORM 132 | Feb 29, 2016168,470917
139 | Objection 140 | Apr 14, 201549,842220
146 | 147 |

148 | npm packages growth in last year 149 |

150 | 151 | With this information, we can take some assumptions. First of all, clearly Sequelize is the most used library. It has been in the game for the longest time and its community haven’t stopped growth. 152 | 153 | It’s interesting to see that Knex is almost as used as Sequelize despite being a less friendly solution. Besides both packages duplicated their weekly downloads this year. Objection, on the other hand, shows practically an almost null increase in their weekly downloads, keeping it around 50K downloads per week. 154 | 155 | Finally, in the last year TypeORM has triplicated its downloads. Despite staying still well below Sequelize and Knex it seems to be positioning itself as an interesting competitor. 156 | 157 | **You can find an updated analisys about npm packages on [NPM Trends](https://www.npmtrends.com/knex-vs-sequelize-vs-objection-vs-typeorm) 🔗**. 158 | 159 | ### Documentation 160 | 161 | Documentation is a really important element when you have to choose a library. On this depends how much easy or painful could be to implement it. 162 | For that reason, we analysed the next points: 163 | 164 | - **Implementation**: Provides a "Getting started" guide, with examples and steps explanations about how to implement the library in my project. 165 | - **Recipes**: Provides examples about how to implement solutions for different case uses (ie: relations). 166 | - **Real-world examples**: Provides real-world examples, with implementations in differents frameworks. 167 | - **Straightforward**: Easy to find needed information and read, updated and clear. 168 | - **TypeScript implementation**: Provides documentation about TypeScript support, with examples and explanations about best practices. 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 213 | 214 | 215 | 216 | 217 | 218 | 219 |
LibraryImplementationRecipesReal-world examplesStraightforwardTypeScript
181 | Sequelize 182 | ⚠️
191 | Knex 192 | ⚠️
201 | TypeORM 202 |
211 | Objection 212 |
220 | 221 | Given all these libraries are used for thousands of people around the globe everyday is not strange that they document the basics on how to implement and start using they. But, for an advanced use, we need more information. 222 | 223 | Based in our research TypeORM and Objection are the best-documented ORMs. Both has an easy to understand, straightforward and user-friendly documentation, they provide real-world examples and recipes for the most common use cases. Besides, they both provide information about TypeScript support, with examples and best practices. 224 | 225 | Either Sequelize and Knex provide TypeScript documentation, it is not enough to implement a clean solution in our projects and requires more research. 226 | 227 | Knex documentation is not bad, but it lacks in official recipes or real-world examples. 228 | 229 | Although Sequelize has a very large documentation, it is difficult to find something in there specially for edge cases. 230 | 231 | ### TypeScript integration 232 | 233 | In the section above, we talk about TypeScript documentation these libraries provide 234 | 235 | All analized libraries expose their own types we can use, but not all has a great integration with TypeScript and developing experience could be a little bit rough with some of them. 236 | 237 | #### Sequelize 👎 238 | 239 | Quoting its [own documentation](https://sequelize.org/master/manual/typescript.html): _"As Sequelize heavily relies on runtime property assignments, TypeScript won't be very useful out of the box. A decent amount of manual type declarations are needed to make models workable."_. Working with Sequelize and TypeScript could be a really bad experience when you are starting with typing in JavaScript. You have to do a lot of manual work to define your models types and made them work in strict mode. 240 | 241 | Relationships are hard with Sequelize and TypeScript. You’ll need to add a set of mixin functions for every single [association](https://sequelize.org/master/manual/associations.html) you create and on both the models involved in the association. If you want to know more about this, you can read [this article which explains how to setup Sequelize with TypeScript](https://vivacitylabs.com/setup-typescript-sequelize/). 242 | 243 | Moreover, when you start deep inside sequelize functionality you will notice many optional properties you can pass some functions are not typed! For example, when you use `bulkCreate` optional property `include` (which should be defined on `BulkCreateOptions` interface) is not created so you have to extend Sequelize type definitions yourself in order to use it. 244 | 245 | #### Knex 👍 246 | 247 | Remember Knex is just a query builder, so we don't define objects that represents our database tables. For this reason, we need to create interfaces for our inputs and outputs. Besides that, **integration between TypeScript and Knex is acceptable**. 248 | 249 | #### TypeORM 🏆 250 | 251 | TypeORM, name already gives us a hint. **It is a perfect partner for TypeScript** and is the one that exploits the most its capabilities. It allows you to write only one TypeScript Class and automatically generates all structure for your entity. 252 | 253 | Code complexity and quantity are greatly reduced, thus our entities definitions are much more cleaner than, for example, Sequelize. 254 | 255 | Decorators can seem strange at first sight, especially if you've never implement this concept before, but once you get used to them they are very easy to use. 256 | 257 | #### Objection 🏆 258 | 259 | **Integration with TypeScript is surprisingly simple and intuitive**. You define your models and Objection automatically generates all structure you need to use them. Models definition are very clean and use them is very straightforward. 260 | 261 | Documentation is very simple and useful and you have [real-world examples as guide](https://github.com/Vincit/objection.js/blob/master/examples/express-ts/). 262 | 263 | Besides, it could be a good alternative if you want to avoid using TypeORM decorators syntax, and keep a more conservative sintax. 264 | 265 | ### Performance 266 | 267 | Probably one of the most controversial topics around ORMs is their performance, thus before start let me do a little disclaimer. This research doesn’t intent to convince you, or not, to use an ORM in your project, there are already a bunch of publications that talk about that. We are going to focus on just in a comparison between our selected libraries uses the use cases we presented before. That said, let's start. 268 | 269 | To perform load tests against our application we used [autocannon](https://github.com/mcollina/autocannon). It is a fast HTTP/1.1 benchmarking tool written in Node.js. All tests were benchmarked using these options: 270 | 271 | ```bash 272 | # Load script for GET (simple) 273 | autocannon "localhost:8080//orders?simple=true" \ 274 | -m "GET" \ 275 | -c 100 -d 20 276 | 277 | # Load script for GET (nested) 278 | autocannon "localhost:8080//orders" \ 279 | -m "GET" \ 280 | -c 100 -d 20 281 | 282 | # Load script for POST 283 | autocannon "localhost:8080//orders" \ 284 | -m "POST" \ 285 | -c 100 -d 20 \ 286 | -i "./data.json" \ 287 | -H "Content-Type: application/json" 288 | 289 | # where = knex|typeorm|sequelize|objection 290 | ``` 291 | 292 | - `-c` The number of concurrent connections to use. 293 | - `-d` The number of pipelined requests to use. 294 | - `-m` The method of the requests. 295 | - `-i` File path for request body. 296 | - `-H` Headers definitions. 297 | 298 | Test Bench Configuration: 299 | 300 | - **OS**: macOs Catalina 10.15.1. 301 | - **CPU**: 2,2 GHz Quad-Core Intel Core i7. 302 | - **RAM**: 16 GB 1600 MHz DDR3. 303 | - **Node version**: v10.17.0. 304 | 305 | #### GET simple 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 |
LibraryLatency avgLatency maxReq/Sec avgReq/Sec minBytes/Sec avgBytes/Sec min
319 | Sequelize 320 | 2201.91 ms2647.18 ms42.9295.83 MB3.94 MB
330 | Knex 🥇 331 | 1276.52 ms ms1647.52 ms75.915810.3 MB7.88 MB
341 | TypeORM 🥈 342 | 1423.56 ms1685.29 ms67.8539.21 MB7.2 MB
352 | Objection 🥉 353 | 1466.76 ms1808.73 ms65.66478.92 MB6.39 MB
362 | 363 |

364 | graphs for GET performance 365 |

366 | 367 | #### GET nested object 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 |
LibraryLatency avgLatency maxReq/Sec avgReq/Sec minBytes/Sec avgBytes/Sec min
381 | Sequelize 382 | 5697.71 ms7124.39 ms14.7565.1 MB2.07 MB
392 | Knex 🥇 393 | 2156.48 ms2471.32 ms43.93511 MB8.8 MB
403 | TypeORM 🥉 404 | 2917.68 ms4586.61 ms31.9148.02 MB3.52 MB
414 | Objection 🥈 415 | 2902.18 ms3672.11 ms32.1158.07 MB3.77 MB
424 | 425 |

426 | graphs for GET performance 427 |

428 | 429 | #### POST nested object 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 |
LibraryLatency avgLatency maxReq/Sec avgReq/Sec minBytes/Sec avgBytes/Sec min
443 | Sequelize 🥉 444 | 882.75 ms1304.18 ms111.27038.9 kB24.3 kB
454 | Knex 🥈 455 | 693.45 ms1166.97 ms141.959150.4 kB32.2 kB
465 | TypeORM 🥇 466 | 477.26 ms2507.41 ms205.953665.2 kB11.2 kB
476 | Objection 477 | 906.97 ms1322.14 ms1087034.1 kB21.9 kB
486 | 487 |

488 | graphs for post performance 489 |

490 | 491 | 492 | ## Developing 493 | 494 | ### Database configuration 495 | 496 | Before running the app, make sure you have [Postgresql installed](https://www.digitalocean.com/community/tutorials/how-to-install-and-use-postgresql-on-ubuntu-14-04) installed. 497 | 498 | You need to create project database manually, to create it run the following steps inside a psql terminal: 499 | 500 | 1. `CREATE DATABASE db_project_name;` 501 | 2. `\c db_project_name` 502 | 3. `CREATE ROLE "project_name" LOGIN CREATEDB PASSWORD 'project_name';` 503 | 504 | Don't forget to create a dotenv file for environment variables. `Dotenv` is used for managing environment variables. They must be stored in a `/.env` file. File structure is described below: 505 | 506 | ``` 507 | DB_HOST=localhost 508 | DB_PORT=5432 509 | DB_USERNAME=project_name 510 | DB_PASSWORD=project_name 511 | DB_NAME=db_project_name 512 | ``` 513 | 514 | ### Migrations 515 | 516 | You need to run migrations before start app. To do it simply run `npm run migrate`. 517 | 518 | ### Starting app 519 | 520 | Run in your terminal: `npm start`. 521 | 522 | ### Debugging 523 | 524 | In order to debug our Node.js application, we enable 'sourceMap' in `tsconfig.json`, this compiler option generates corresponding `.map` files from original Javascipt counterpart. This change is mandatory to attach a debugger, otherwise it wouldn't be able to match transpiled files with their originals. 525 | 526 | In VSCode, you will need to add an `./.vscode/launch.json` file in order to launch the debugger. You can use the following: 527 | 528 | ```json 529 | { 530 | // Use IntelliSense to learn about possible attributes. 531 | // Hover to view descriptions of existing attributes. 532 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 533 | "version": "0.2.0", 534 | "configurations": [ 535 | { 536 | "type": "node", 537 | "request": "launch", 538 | "name": "Launch Program", 539 | "program": "${workspaceFolder}/server.ts", 540 | "preLaunchTask": "tsc: build - tsconfig.json", 541 | "internalConsoleOptions": "neverOpen", 542 | "console": "integratedTerminal", 543 | "disableOptimisticBPs": true, 544 | "outFiles": ["${workspaceFolder}/dist/**/*.js"] 545 | } 546 | ] 547 | } 548 | ``` 549 | 550 | ## License 551 | 552 | This project is written and maintained by [Emanuel Casco](https://github.com/emanuelcasco) and is available under the MIT [license](LICENSE.md). 553 | 554 | Copyright (c) 2019 Emanuel Casco 555 | 556 | Permission is hereby granted, free of charge, to any person obtaining a copy 557 | of this software and associated documentation files (the "Software"), to deal 558 | in the Software without restriction, including without limitation the rights 559 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 560 | copies of the Software, and to permit persons to whom the Software is 561 | furnished to do so, subject to the following conditions: 562 | 563 | The above copyright notice and this permission notice shall be included in 564 | all copies or substantial portions of the Software. 565 | 566 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 567 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 568 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 569 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 570 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 571 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 572 | THE SOFTWARE. 573 | -------------------------------------------------------------------------------- /assets/db.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emanuelcasco/typescript-orm-benchmark/76dbc27194afd4c90c6049eae00d5df96feba5db/assets/db.png -------------------------------------------------------------------------------- /assets/npm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emanuelcasco/typescript-orm-benchmark/76dbc27194afd4c90c6049eae00d5df96feba5db/assets/npm.png -------------------------------------------------------------------------------- /assets/performance-get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emanuelcasco/typescript-orm-benchmark/76dbc27194afd4c90c6049eae00d5df96feba5db/assets/performance-get.png -------------------------------------------------------------------------------- /assets/performance-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emanuelcasco/typescript-orm-benchmark/76dbc27194afd4c90c6049eae00d5df96feba5db/assets/performance-post.png -------------------------------------------------------------------------------- /assets/performance-simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emanuelcasco/typescript-orm-benchmark/76dbc27194afd4c90c6049eae00d5df96feba5db/assets/performance-simple.png -------------------------------------------------------------------------------- /data.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "user": "example user", 4 | "items": [{ "name": "example item", "value": 99.99 }] 5 | } 6 | ] 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "typescript-orm-benchmark", 3 | "version": "0.1.0", 4 | "description": "Example base for typescript orm benchmark", 5 | "engines": { 6 | "node": "10.17.0", 7 | "npm": "6.4.1" 8 | }, 9 | "scripts": { 10 | "build": "tsc", 11 | "lint": "tsc --noEmit && eslint \"*/**/*.ts\"", 12 | "lint-diff": "git diff --name-only --cached --relative | grep \\\\.ts$ | xargs eslint", 13 | "postinstall": "npm run build", 14 | "precommit": "npm run lint-diff", 15 | "knex": "node -r dotenv/config ./node_modules/.bin/knex", 16 | "migrate:make": "npm run knex -- migrate:make --knexfile ./dist/knex/config.js", 17 | "migrate": "npm run knex -- migrate:latest --knexfile ./dist/knex/config.js", 18 | "start": "ts-node-dev --respawn --transpileOnly ./server.ts" 19 | }, 20 | "cacheDirectories": [ 21 | "node_modules" 22 | ], 23 | "main": "dist/server.js", 24 | "author": "Emanuel Casco", 25 | "license": "MIT", 26 | "dependencies": { 27 | "autocannon": "^4.3.0", 28 | "body-parser": "^1.18.2", 29 | "cors": "^2.8.4", 30 | "express": "^4.17.1", 31 | "knex": "^0.20.0", 32 | "knex-stringcase": "^1.3.0", 33 | "objection": "^1.6.11", 34 | "pg": "^7.4.1", 35 | "reflect-metadata": "^0.1.13", 36 | "sequelize": "^5.21.2", 37 | "typeorm": "^0.2.20" 38 | }, 39 | "devDependencies": { 40 | "@types/babel-core": "^6.25.6", 41 | "@types/babel-traverse": "^6.25.5", 42 | "@types/bluebird": "^3.5.27", 43 | "@types/body-parser": "^1.17.0", 44 | "@types/express": "^4.17.0", 45 | "@types/node": "^12.6.2", 46 | "@types/pg": "^7.4.14", 47 | "@types/sequelize": "^4.28.3", 48 | "@types/validator": "^10.11.3", 49 | "@typescript-eslint/eslint-plugin": "^2.6.1", 50 | "@typescript-eslint/eslint-plugin-tslint": "^2.6.1", 51 | "@typescript-eslint/parser": "^2.3.1", 52 | "babel": "6.23.0", 53 | "babel-core": "6.26.0", 54 | "babel-eslint": "^8.2.2", 55 | "babel-jest": "^25.0.0", 56 | "babel-preset-env": "^1.7.0", 57 | "dictum.js": "^1.0.0", 58 | "dotenv": "^5.0.1", 59 | "eslint": "^5.16.0", 60 | "eslint-plugin-prettier": "^3.1.1", 61 | "husky": "^0.14.3", 62 | "sequelize-cli": "^5.5.1", 63 | "ts-node-dev": "1.0.0-pre.40", 64 | "tslint": "^5.20.1", 65 | "typescript": "^3.7.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Summary 2 | 3 | [Change!] Describe your feature, problems you had, notes, improvements and others. 4 | 5 | ## Known Issues 6 | 7 | [Change!] List any known issue of the feature you are implementing. 8 | 9 | ## Trello Card 10 | 11 | [Change!] Link to the associated Trello card. 12 | -------------------------------------------------------------------------------- /server.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | 4 | import knex from './src/knex'; 5 | import objection from './src/objection'; 6 | import sequelize from './src/sequelize'; 7 | 8 | import { initializeTypeOrm } from './src/typeorm' 9 | 10 | const DEFAULT_BODY_SIZE_LIMIT = 1024 * 1024 * 10; 11 | const DEFAULT_PARAMETER_LIMIT = 10000; 12 | const PORT = 8080; 13 | 14 | const app = express(); 15 | 16 | // Client must send "Content-Type: application/json" header 17 | app.use(bodyParser.json({ 18 | limit: DEFAULT_BODY_SIZE_LIMIT 19 | })); 20 | 21 | app.use(bodyParser.urlencoded({ 22 | extended: true, 23 | parameterLimit: DEFAULT_PARAMETER_LIMIT 24 | })); 25 | 26 | app.use('/sequelize', sequelize); 27 | app.use('/objection', objection); 28 | app.use('/knex', knex); 29 | 30 | Promise.resolve() 31 | .then(() => initializeTypeOrm()) 32 | .then(typeOrmRouter => { 33 | app.use('/typeorm', typeOrmRouter); 34 | }) 35 | .then(() => { 36 | app.listen(PORT); 37 | console.log(`Listening on port: ${PORT}`); 38 | }) 39 | .catch(console.error); 40 | -------------------------------------------------------------------------------- /src/knex/config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import path from 'path'; 4 | 5 | const configuration = { 6 | client: 'pg', 7 | pool: { min: 0, max: 10 }, 8 | debug: true, 9 | acquireConnectionTimeout: 50000, 10 | migrations: { 11 | tableName: 'knex_migrations', 12 | directory: path.resolve(__dirname, './migrations') 13 | }, 14 | connection: { 15 | host: process.env.DB_HOST as string, 16 | user: process.env.DB_USER as string, 17 | password: process.env.DB_PASSWORD as string, 18 | database: process.env.DB_NAME as string, 19 | port: process.env.DB_PORT as string, 20 | connectionTimeout: 10000 21 | }, 22 | timezone: 'UTC', 23 | } as import('knex').Config; 24 | 25 | export = configuration; 26 | -------------------------------------------------------------------------------- /src/knex/db.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | import knexStringcase from 'knex-stringcase'; 3 | 4 | import * as knexConfig from './config'; 5 | 6 | export type Transaction = Knex.Transaction; 7 | 8 | export const knex = Knex(knexStringcase(knexConfig)); 9 | 10 | export const TABLES: { [key: string]: string } = { 11 | ORDER: 'orders', 12 | ITEM: 'items', 13 | ORDER_ITEM: 'orders_items' 14 | }; 15 | -------------------------------------------------------------------------------- /src/knex/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response } from 'express'; 2 | 3 | import { knex, Transaction, TABLES } from './db'; 4 | import { NextFunction } from 'connect'; 5 | 6 | import { 7 | Item, 8 | Order, 9 | OrderPayload, 10 | OrderResponse 11 | } from './interfaces'; 12 | 13 | const router = Router(); 14 | 15 | router.get('/orders', async (req: Request, res: Response, next: NextFunction) => { 16 | try { 17 | const { simple } = req.query; 18 | let orders: OrderResponse[]; 19 | if (simple) { 20 | orders = await knex(TABLES.ORDER).select('orders.*'); 21 | } else { 22 | orders = await knex(TABLES.ORDER) 23 | .select( 24 | 'orders.*', 25 | knex.raw(`json_agg(items.*) AS items`) 26 | ) 27 | .leftJoin('orders_items', 'orders.id', 'orders_items.order_id') 28 | .leftJoin('items', 'orders_items.item_id', 'items.id') 29 | .groupBy('orders.id'); 30 | } 31 | return res.status(200).send({ orders }); 32 | } catch (error) { 33 | return next(Promise.reject('Error processing values')); 34 | } 35 | }); 36 | 37 | router.post('/orders', async (req: Request, res: Response, next: NextFunction) => { 38 | const ordersPayload: OrderPayload[] = req.body; 39 | const trx: Transaction = await knex.transaction(); 40 | try { 41 | const promises = ordersPayload.map(async payload => { 42 | const [order] = await trx(TABLES.ORDER) 43 | .insert({ user: payload.user }) 44 | .returning('*'); 45 | const items = await trx(TABLES.ITEM) 46 | .insert(payload.items) 47 | .returning('*'); 48 | await trx(TABLES.ORDER_ITEM) 49 | .insert(items.map(item => ({ 50 | itemId: item.id, 51 | orderId: order.id 52 | }))) 53 | .returning('*'); 54 | return { ...order, items }; 55 | }); 56 | 57 | const orders: OrderResponse[] = await Promise.all(promises); 58 | await trx.commit(); 59 | return res.status(201).send({ orders }); 60 | } catch (error) { 61 | await trx.rollback(); 62 | return next(Promise.reject('Error processing values')); 63 | } 64 | }); 65 | 66 | export default router; 67 | -------------------------------------------------------------------------------- /src/knex/interfaces.ts: -------------------------------------------------------------------------------- 1 | export type ItemRaw = { name: string; price: number }; 2 | export type Item = { id: number } & ItemRaw; 3 | 4 | export type OrderRaw = { user: string }; 5 | export type Order = { id: number; user: string; date: number }; 6 | 7 | export type OrderPayload = OrderRaw & { items: ItemRaw[] }; 8 | export type OrderResponse = Order & { items: Item[] }; 9 | 10 | -------------------------------------------------------------------------------- /src/knex/migrations/20160911120323_create_tables.ts: -------------------------------------------------------------------------------- 1 | import * as Knex from 'knex'; 2 | 3 | exports.up = async (knex: Knex): Promise => { 4 | await knex.schema.createTable("orders", (table: Knex.TableBuilder) => { 5 | table.increments("id").primary(); 6 | table.string("user"); 7 | table.timestamp("date").defaultTo(knex.fn.now()); 8 | }); 9 | await knex.schema.createTable("items", (table: Knex.TableBuilder) => { 10 | table.increments("id").primary(); 11 | table.string("name"); 12 | table.float("value"); 13 | }); 14 | await knex.schema.createTable("orders_items", (table: Knex.TableBuilder) => { 15 | table.increments("id").primary(); 16 | table.integer("order_id").references("orders.id"); 17 | table.integer("item_id").references("items.id"); 18 | }); 19 | }; 20 | 21 | exports.down = async (knex: Knex): Promise => { 22 | await knex.schema.dropTable("orders_items"); 23 | await knex.schema.dropTable("orders"); 24 | await knex.schema.dropTable("items"); 25 | }; 26 | -------------------------------------------------------------------------------- /src/objection/config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import path from 'path'; 4 | 5 | const configuration = { 6 | client: 'pg', 7 | pool: { min: 0, max: 10 }, 8 | debug: true, 9 | acquireConnectionTimeout: 50000, 10 | migrations: { 11 | tableName: 'knex_migrations', 12 | directory: path.resolve(__dirname, './migrations') 13 | }, 14 | connection: { 15 | host: process.env.DB_HOST as string, 16 | user: process.env.DB_USER as string, 17 | password: process.env.DB_PASSWORD as string, 18 | database: process.env.DB_NAME as string, 19 | port: process.env.DB_PORT as string, 20 | connectionTimeout: 10000 21 | }, 22 | timezone: 'UTC', 23 | } as import('knex').Config; 24 | 25 | export = configuration; 26 | -------------------------------------------------------------------------------- /src/objection/db.ts: -------------------------------------------------------------------------------- 1 | import Knex from 'knex'; 2 | import { Model, RelationMapping, transaction } from 'objection'; 3 | 4 | import * as knexConfig from './config'; 5 | 6 | // Give the knex instance to objection. 7 | Model.knex(Knex(knexConfig)); 8 | 9 | export const Transaction = transaction; 10 | 11 | export class Item extends Model { 12 | static get tableName(): string { 13 | return "items"; 14 | } 15 | } 16 | 17 | export class Order extends Model { 18 | static get tableName(): string { 19 | return "orders"; 20 | } 21 | 22 | static get relationMappings(): { items: RelationMapping } { 23 | return { 24 | items: { 25 | relation: Model.ManyToManyRelation, 26 | modelClass: Item, 27 | join: { 28 | from: "orders.id", 29 | through: { 30 | from: "orders_items.order_id", 31 | to: "orders_items.item_id" 32 | }, 33 | to: "items.id" 34 | } 35 | } 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/objection/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response, NextFunction } from 'express'; 2 | 3 | import { Order, Transaction } from './db'; 4 | 5 | const router = Router(); 6 | 7 | router.get('/orders', async (req: Request, res: Response, next: NextFunction) => { 8 | const { simple } = req.query; 9 | try { 10 | const query = simple ? Order.query() : Order.query().joinEager('items'); 11 | 12 | const orders = await query; 13 | return res.status(200).send({ orders }); 14 | } catch (error) { 15 | return next(Promise.reject('Error processing values')); 16 | } 17 | }); 18 | 19 | router.post('/orders', async (req: Request, res: Response, next: NextFunction) => { 20 | const ordersPayload: Order[] = req.body; 21 | const trx = await Transaction.start(Order.knex()); 22 | try { 23 | const orders = await Order.query(trx).insertGraph(ordersPayload); 24 | await trx.commit(); 25 | return res.status(200).send({ orders }); 26 | } catch (error) { 27 | await trx.rollback(); 28 | return next(Promise.reject('Error processing values')); 29 | } 30 | }); 31 | 32 | export default router; 33 | -------------------------------------------------------------------------------- /src/objection/migrations/20160911120323_create_tables.ts: -------------------------------------------------------------------------------- 1 | import * as Knex from 'knex'; 2 | 3 | exports.up = async (knex: Knex): Promise => { 4 | await knex.schema.createTable("orders", (table: Knex.TableBuilder) => { 5 | table.increments("id").primary(); 6 | table.string("user"); 7 | table.timestamp("date").defaultTo(knex.fn.now()); 8 | }); 9 | await knex.schema.createTable("items", (table: Knex.TableBuilder) => { 10 | table.increments("id").primary(); 11 | table.string("name"); 12 | table.float("value"); 13 | }); 14 | await knex.schema.createTable("orders_items", (table: Knex.TableBuilder) => { 15 | table.increments("id").primary(); 16 | table.integer("order_id").references("orders.id"); 17 | table.integer("item_id").references("items.id"); 18 | }); 19 | }; 20 | 21 | exports.down = async (knex: Knex): Promise => { 22 | await knex.schema.dropTable("orders_items"); 23 | await knex.schema.dropTable("orders"); 24 | await knex.schema.dropTable("items"); 25 | }; 26 | -------------------------------------------------------------------------------- /src/sequelize/config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | const config = { 4 | host: process.env.DB_HOST, 5 | port: Number(process.env.DB_PORT), 6 | database: process.env.DB_NAME as string, 7 | username: process.env.DB_USER as string, 8 | password: process.env.DB_PASSWORD as string, 9 | dialect: 'postgres', 10 | define: { 11 | freezeTableName: true, 12 | timestamps: false 13 | }, 14 | logging: console.log 15 | }; 16 | 17 | export default config; 18 | -------------------------------------------------------------------------------- /src/sequelize/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response, NextFunction } from 'express'; 2 | 3 | import db from './models'; 4 | 5 | const { Order, Item, sequelize } = db; 6 | 7 | const router = Router(); 8 | 9 | router.get( 10 | '/orders', 11 | async (req: Request, res: Response, next: NextFunction) => { 12 | const { simple } = req.query; 13 | 14 | const options = simple 15 | ? {} 16 | : { include: [{ model: Item, as: 'items' }] }; 17 | try { 18 | const orders = await Order.findAll(options); 19 | return res.status(200).send({ orders }); 20 | } catch (error) { 21 | return next('Error processing values'); 22 | } 23 | } 24 | ); 25 | 26 | router.post( 27 | '/orders', 28 | async (req: Request, res: Response, next: NextFunction) => { 29 | const ordersPayload = req.body; 30 | 31 | const trx = await sequelize.transaction(); 32 | try { 33 | const orders = await Order.bulkCreate(ordersPayload, { 34 | include: [{ model: Item, as: 'items' }], 35 | validate: true, 36 | transaction: trx 37 | }); 38 | await trx.commit(); 39 | return res.status(200).send({ orders }); 40 | } catch (error) { 41 | if (trx) await trx.rollback(); 42 | return next('Error processing values'); 43 | } 44 | } 45 | ); 46 | 47 | export default router; 48 | -------------------------------------------------------------------------------- /src/sequelize/models/Item.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | interface ItemAttributes { 4 | id?: number; 5 | name: string; 6 | value: number; 7 | } 8 | 9 | interface ItemInstance 10 | extends Sequelize.Instance, 11 | ItemAttributes {} 12 | 13 | export type ItemModel = Sequelize.Model; 14 | 15 | export const ItemFactory = ( 16 | sequelize: Sequelize.Sequelize, 17 | DataTypes: Sequelize.DataTypes 18 | ): ItemModel => { 19 | const Item: ItemModel = sequelize.define( 20 | 'items', 21 | { 22 | id: { 23 | type: DataTypes.STRING, 24 | autoIncrement: true, 25 | primaryKey: true 26 | }, 27 | name: { type: DataTypes.STRING }, 28 | value: { type: DataTypes.NUMERIC } 29 | } 30 | ); 31 | 32 | Item.associate = (models): void => { 33 | Item.belongsToMany(models.Order, { 34 | through: 'orders_items', 35 | as: 'orders', 36 | foreignKey: 'item_id' 37 | }); 38 | }; 39 | 40 | return Item; 41 | }; 42 | -------------------------------------------------------------------------------- /src/sequelize/models/Order.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | interface OrderAttributes { 4 | id?: number; 5 | user: string; 6 | date: number; 7 | } 8 | 9 | interface OrderInstance 10 | extends Sequelize.Instance, 11 | OrderAttributes {} 12 | 13 | export type OrderModel = Sequelize.Model; 14 | 15 | export const OrderFactory = ( 16 | sequelize: Sequelize.Sequelize, 17 | DataTypes: Sequelize.DataTypes 18 | ): OrderModel => { 19 | const Order: OrderModel = sequelize.define( 20 | 'orders', 21 | { 22 | id: { 23 | type: DataTypes.STRING, 24 | autoIncrement: true, 25 | primaryKey: true 26 | }, 27 | user: { type: DataTypes.STRING }, 28 | date: { 29 | type: DataTypes.DATE, 30 | defaultValue: Sequelize.NOW 31 | } 32 | } 33 | ); 34 | 35 | Order.associate = (models): void => { 36 | Order.belongsToMany(models.Item, { 37 | through: 'orders_items', 38 | as: 'items', 39 | foreignKey: 'order_id' 40 | }) 41 | }; 42 | 43 | return Order; 44 | }; 45 | -------------------------------------------------------------------------------- /src/sequelize/models/index.ts: -------------------------------------------------------------------------------- 1 | import Sequelize from 'sequelize'; 2 | 3 | import config from '../config'; 4 | import * as Order from './Order'; 5 | import * as Item from './Item'; 6 | 7 | export interface ModelsInterface { 8 | Order: Order.OrderModel; 9 | Item: Item.ItemModel; 10 | } 11 | 12 | export interface DbInterface extends ModelsInterface { 13 | sequelize: Sequelize.Sequelize; 14 | Sequelize: Sequelize.SequelizeStatic; 15 | } 16 | 17 | const { database, username, password, ...params } = config; 18 | const sequelize = new Sequelize(database, username, password, params); 19 | 20 | const models: ModelsInterface = { 21 | Order: Order.OrderFactory(sequelize, Sequelize), 22 | Item: Item.ItemFactory(sequelize, Sequelize) 23 | } 24 | 25 | Object.keys(models).forEach(modelName => { 26 | if (models[modelName].associate) { 27 | models[modelName].associate(models); 28 | } 29 | }); 30 | 31 | const db: DbInterface = { 32 | sequelize, 33 | Sequelize, 34 | ...models 35 | }; 36 | 37 | export default db; -------------------------------------------------------------------------------- /src/typeorm/config.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config'; 2 | 3 | import path from 'path'; 4 | 5 | const configuration = { 6 | type: 'postgres', 7 | port: process.env.DB_PORT, 8 | host: process.env.DB_HOST, 9 | username: process.env.DB_USER, 10 | password: process.env.DB_PASSWORD, 11 | database: process.env.DB_NAME, 12 | entities: [path.resolve(__dirname, './entities/*')], 13 | logging: true 14 | } as import('typeorm').ConnectionOptions; 15 | 16 | export default configuration; 17 | -------------------------------------------------------------------------------- /src/typeorm/db.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ConnectionOptions, 3 | createConnection, 4 | getManager, 5 | Repository 6 | } from 'typeorm'; 7 | 8 | import { Order as OrderEntity } from './entities/Order'; 9 | import { Item as ItemEntity } from './entities/Item'; 10 | 11 | export type Repositories = { 12 | ItemRepository: Repository; 13 | OrderRepository: Repository; 14 | }; 15 | 16 | export async function initRepositories( 17 | typeOrmConfig: ConnectionOptions 18 | ): Promise { 19 | await createConnection(typeOrmConfig); 20 | return { 21 | ItemRepository: getManager().getRepository(ItemEntity), 22 | OrderRepository: getManager().getRepository(OrderEntity) 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /src/typeorm/entities/Item.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; 2 | 3 | @Entity({name: "items"}) 4 | export class Item { 5 | @PrimaryGeneratedColumn() 6 | id: number; 7 | 8 | @Column() 9 | name: string; 10 | 11 | @Column() 12 | value: number; 13 | } 14 | -------------------------------------------------------------------------------- /src/typeorm/entities/Order.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Entity, 4 | JoinTable, 5 | ManyToMany, 6 | PrimaryGeneratedColumn 7 | } from 'typeorm'; 8 | 9 | import { Item } from './Item'; 10 | 11 | @Entity({ name: 'orders' }) 12 | export class Order { 13 | @PrimaryGeneratedColumn() 14 | id: number; 15 | 16 | @Column() 17 | user: string; 18 | 19 | @Column() 20 | date: number; 21 | 22 | @ManyToMany(() => Item, { cascade: true }) 23 | @JoinTable({ 24 | name: 'orders_items', 25 | joinColumn: { name: 'order_id', referencedColumnName: 'id' }, 26 | inverseJoinColumn: { name: 'item_id', referencedColumnName: 'id' }, 27 | }) 28 | items: Item[]; 29 | } 30 | -------------------------------------------------------------------------------- /src/typeorm/index.ts: -------------------------------------------------------------------------------- 1 | import { Router, Request, Response, NextFunction } from 'express'; 2 | 3 | import { initRepositories } from './db'; 4 | import config from './config'; 5 | 6 | const router = Router(); 7 | 8 | export async function initializeTypeOrm(): Promise { 9 | const { OrderRepository } = await initRepositories(config); 10 | 11 | router.get( 12 | '/orders', 13 | async (req: Request, res: Response, next: NextFunction) => { 14 | const { simple } = req.query; 15 | 16 | const options = simple 17 | ? {} 18 | : { relations: ['items'] }; 19 | try { 20 | const orders = await OrderRepository.find(options); 21 | return res.status(200).send({ orders }); 22 | } catch (error) { 23 | return next('Error processing values'); 24 | } 25 | } 26 | ); 27 | 28 | router.post( 29 | '/orders', 30 | async (req: Request, res: Response, next: NextFunction) => { 31 | try { 32 | const orders = OrderRepository.create(req.body); 33 | await OrderRepository.save(orders); 34 | return res.status(200).send({ orders }); 35 | } catch (error) { 36 | return next('Error processing values'); 37 | } 38 | } 39 | ); 40 | 41 | return router; 42 | } 43 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "baseUrl": ".", 5 | "checkJs": false, 6 | "esModuleInterop": true, 7 | "emitDecoratorMetadata": true, 8 | "experimentalDecorators": true, 9 | "downlevelIteration": true, 10 | "lib": ["esnext", "dom"], 11 | "module": "commonjs", 12 | "noUnusedLocals": true, 13 | "outDir": "dist", 14 | "skipLibCheck": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "strictFunctionTypes": false, 18 | "strictPropertyInitialization": false, 19 | "suppressImplicitAnyIndexErrors": true, 20 | "target": "esnext", 21 | "paths": { 22 | "*": ["node_modules/@types/*", "@types/*"] 23 | } 24 | }, 25 | "include": ["./"], 26 | "exclude": ["**/*.spec.ts", "**/*.test.ts", "dist"] 27 | } 28 | --------------------------------------------------------------------------------