UNPKG

32.6 kBMarkdownView Raw
1[![npm version](https://badge.fury.io/js/typescript-rest.svg)](https://badge.fury.io/js/typescript-rest)
2[![Build Status](https://travis-ci.org/thiagobustamante/typescript-rest.svg?branch=master)](https://travis-ci.org/thiagobustamante/typescript-rest)
3[![Coverage Status](https://coveralls.io/repos/github/thiagobustamante/typescript-rest/badge.svg?branch=master)](https://coveralls.io/github/thiagobustamante/typescript-rest?branch=master)
4[![Known Vulnerabilities](https://snyk.io/test/github/thiagobustamante/typescript-rest/badge.svg?targetFile=package.json)](https://snyk.io/test/github/thiagobustamante/typescript-rest?targetFile=package.json)
5
6# REST Services for Typescript
7This is a lightweight annotation-based [expressjs](http://expressjs.com/) extension for typescript.
8
9It can be used to define your APIs using ES7 decorators.
10
11**Project Sponsors**
12
13This project is supported by [Leanty](https://github.com/Leanty/)'s team and is widely used by its main product: The [Tree Gateway](http://www.treegateway.org) API Gateway.
14
15**Table of Contents**
16
17- [REST Services for Typescript](#)
18 - [Installation](#installation)
19 - [Configuration](#configuration)
20 - [Basic Usage](#basic-usage)
21 - [Boilerplate Project](#boilerplate-project)
22 - [Complete Guide](#complete-guide)
23 - [Server](#server)
24 - [Registering Services](#registering-services)
25 - [@Path Decorator](#path-decorator)
26 - [Path Parameters](#path-parameters)
27 - [Http Methods](#http-methods)
28 - [Parameters](#parameters)
29 - [Service Context](#service-context)
30 - [Service Return](#service-return)
31 - [Asynchronous services](#asynchronous-services)
32 - [Errors](#errors)
33 - [BodyParser Options](#bodyparser-options)
34 - [Types and languages](#types-and-languages)
35 - [IoC](#ioc)
36 - [Inheritance and abstract services](#inheritance-and-abstract-services)
37 - [Preprocessors](#preprocessors)
38 - [Swagger](#swagger)
39 - [Breaking Changes - 1.0.0](#breaking-changes)
40
41## Installation
42
43This library only works with typescript. Ensure it is installed:
44
45```bash
46npm install typescript -g
47```
48
49To install typescript-rest:
50
51```bash
52npm install typescript-rest --save
53```
54
55## Configuration
56
57Typescript-rest requires the following TypeScript compilation options in your tsconfig.json file:
58
59```typescript
60{
61 "compilerOptions": {
62 "experimentalDecorators": true,
63 "emitDecoratorMetadata": true
64 }
65}
66```
67
68## Basic Usage
69
70```typescript
71import * as express from "express";
72import {Server, Path, GET, PathParam} from "typescript-rest";
73
74@Path("/hello")
75class HelloService {
76 @Path(":name")
77 @GET
78 sayHello( @PathParam('name') name: string): string {
79 return "Hello " + name;
80 }
81}
82
83let app: express.Application = express();
84Server.buildServices(app);
85
86app.listen(3000, function() {
87 console.log('Rest Server listening on port 3000!');
88});
89
90```
91
92That's it. You can just call now:
93
94```
95GET http://localhost:3000/hello/joe
96```
97
98## Boilerplate Project
99
100You can check [this project](https://github.com/vrudikov/typescript-rest-boilerplate) to get started.
101
102## Complete Guide
103
104This library allows you to use ES7 decorators to configure your services using
105expressjs.
106
107### Server
108
109The Server class is used to configure the server, like:
110
111```typescript
112let app: express.Application = express();
113Server.setFileDest('/uploads');
114Server.buildServices(app);
115app.listen(3000, function() {
116 console.log('Rest Server listening on port 3000!');
117});
118```
119
120Note that Server receives an ```express.Router``` instance. Then it configures
121all the routes based on the decorators used on your classes.
122
123So, you can use also any other expressjs feature, like error handlers, middlewares etc
124without any restriction.
125
126#### Registering Services
127
128When you call:
129
130```typescript
131Server.buildServices(app);
132```
133
134The service will expose all services that can be found in the imported module into the express router provided. But it is possible to choose which services you want to expose.
135
136```typescript
137import * as express from "express";
138import {Server} from "typescript-rest";
139import {ServiceOne} from "./service-one";
140import {ServiceTwo} from "./service-two";
141import {ServiceThree} from "./service-three";
142
143let app: express.Application = express();
144Server.buildServices(app, ServiceOne, ServiceTwo, ServiceThree);
145```
146
147It is possible to use multiples routers:
148
149
150```typescript
151Server.buildServices(adminRouter, ...adminApi);
152Server.buildServices(app, ServiceOne);
153```
154
155And it is, also, possible to use glob expressions to point where your services are:
156
157```typescript
158const app = express();
159
160const apis = express.Router();
161const admin = express.Router();
162
163Server.loadServices(apis, 'lib/controllers/apis/*');
164Server.loadServices(admin, 'lib/controllers/admin/*');
165
166app.use('apis', apis);
167app.use('admin', admin);
168```
169
170That will register all services exported by any file located under ```lib/controllers/apis``` in the ```apis``` router and services in ```lib/controllers/admin``` in the ```admin``` router.
171
172Negation is also supported in the glob patterns:
173
174```typescript
175 Server.loadServices(app, ['lib/controllers/*', '!**/exclude*']);
176 // includes all controllers, excluding that one which name starts with 'exclude'
177```
178
179And it is possilbe to inform a base folder for the patterns:
180
181```typescript
182 Server.loadServices(app, 'controllers/*', `${__dirname}/..`]);
183 // Inform a folder as origin for the patterns
184```
185
186### @Path Decorator
187
188The @Path decorator allow us to define a router path for a given endpoint.
189Route paths, in combination with a request method, define the endpoints at
190which requests can be made. Route paths can be strings, string patterns, or regular expressions.
191
192The characters ?, +, *, and () are subsets of their regular expression counterparts.
193The hyphen (-) and the dot (.) are interpreted literally by string-based paths.
194
195
196*We use [path-to-regexp](https://www.npmjs.com/package/path-to-regexp) for matching the
197route paths; see the path-to-regexp documentation for all the possibilities in defining route paths.*
198
199
200Some examples:
201
202```typescript
203@Path("/hello")
204class HelloService {
205}
206```
207
208```typescript
209@Path("/test/hello")
210class TestService {
211}
212```
213
214This route path will match acd and abcd:
215
216```typescript
217@Path("ab?cd")
218class TestService {
219}
220```
221
222This route path will match abcd, abbcd, abbbcd, and so on:
223
224```typescript
225@Path("ab+cd")
226class TestService {
227}
228```
229
230This route path will match abcd, abxcd, abRANDOMcd, ab123cd, and so on:
231
232```typescript
233@Path("ab*cd")
234class TestService {
235}
236```
237
238This route path will match /abe and /abcde:
239
240```typescript
241@Path("/ab(cd)?e")
242class TestService {
243}
244```
245
246This route path will match butterfly and dragonfly, but not butterflyman, dragonfly man, and so on:
247
248```typescript
249@Path("/.*fly$/")
250class TestService {
251}
252```
253#### Path Parameters
254
255Route parameters are named URL segments that are used to capture the values specified at their position in the URL.
256The captured values are populated in the req.params object, with the name of the route parameter specified in
257the path as their respective keys. They can be refered through @PathParam decorator on a service method argument.
258
259Some examples:
260
261```typescript
262@Path("/users")
263class UserService {
264 @Path("/:userId/books/:bookId")
265 @GET
266 getUserBook(@PathParam("userId") userId: number, @PathParam("bookId") bookId: number): Promise<Book> {
267 //...
268 }
269}
270```
271The requested URL http://localhost:3000/users/34/books/8989 would map the parameters as:
272
273```
274 userId: "34"
275 bookId: "8989"
276```
277
278Since the hyphen (-) and the dot (.) are interpreted literally, they can be used along with route
279parameters for useful purposes.
280
281```
282Route path: /flights/:from-:to
283Request URL: http://localhost:3000/flights/LAX-SFO
284req.params: { "from": "LAX", "to": "SFO" }
285```
286
287```
288Route path: /plantae/:genus.:species
289Request URL: http://localhost:3000/plantae/Prunus.persica
290req.params: { "genus": "Prunus", "species": "persica" }
291```
292
293### Http Methods
294
295We have decorators for each HTTP method. Theses decorators are used on service methods already bound
296to a Path route to specify the endpoint at which requests can be made.
297
298The following decorators can be used:
299
300 - @GET
301 - @POST
302 - @PUT
303 - @PATCH
304 - @DELETE
305 - @OPTIONS
306 - @HEAD
307
308Some examples:
309
310```typescript
311@Path("/users")
312class UserService {
313 @GET
314 getUsers(): Promise<Array<User>> {
315 //...
316 }
317
318 @GET
319 @Path(":userId")
320 getUser(@PathParam("userId")): Promise<User> {
321 //...
322 }
323
324 @PUT
325 @Path(":userId")
326 saveUser(@PathParam("userId"), user: User): void {
327 //...
328 }
329}
330```
331
332Only methods decorated with one of this HTTP method decorators are exposed as handlers for
333requests on the server.
334
335A single method can only be decorated with one of those decorators at a time.
336
337### Parameters
338
339There are decorators to map parameters to arguments on service methods. Each decorator can map a
340differente kind of parameter on request.
341
342The following decorators are available:
343
344Decorator | Description
345--------- | -----------
346@PathParam | Parameter in requested URL path
347@QueryParam | Parameter in the query string
348@FormParam | Parameter in an HTML form
349@HeaderParam | Parameter in the request header
350@CookieParam | Parameter in a cookie
351@FileParam | A File in a multipart form
352@FilesParam | An array of Files in a multipart form
353@Param | Parameter in the query string or in an HTML form
354
355Some examples:
356
357```typescript
358@Path("/sample")
359class Sample {
360 @GET
361 test(@QueryParam("limit") limit:number, @QueryParam("skip") skip:number) {
362 //...
363 // GET http://domain/sample?limit=5&skip=10
364 }
365
366 @POST
367 test(@FormParam("name") name:string) {
368 //...
369 // POST http://domain/sample
370 // body: name=joe
371 }
372
373 @POST
374 @Path("upload")
375 testUploadFile( @FileParam("myFile") file: Express.Multer.File,
376 @FormParam("myField") myField: string) {
377 //...
378 /* POST http://domain/sample/upload
379 Content-Type: multipart/form-data; boundary=AaB03x
380
381 --AaB03x
382 Content-Disposition: form-data; name="myField"
383
384 Field Value
385 --AaB03x
386 Content-Disposition: form-data; name="myFile"; filename="file1.txt"
387 Content-Type: text/plain
388
389 ... contents of file1.txt ...
390 --AaB03x--
391 */
392 }
393}
394```
395
396An argument that has no decorator is handled as a json serialized entity in the request body
397
398```typescript
399@Path("/sample")
400class Sample {
401 @POST
402 test(user: User) {
403 //...
404 // POST http://domain/sample
405 // body: a json representation of the User object
406 }
407}
408```
409
410The ``` @*Param ``` decorators can also be used on service class properties.
411
412An example:
413
414```typescript
415 @Path("users/:userId/photos")
416 class TestService {
417 @PathParam('userId')
418 userId: string;
419
420 @GET
421 getPhoto(@PathParam('photoId')) {
422 // Get the photo and return
423 }
424 }
425```
426
427
428### Service Context
429
430A Context object is created to group informations about the current request being handled.
431This Context can be accessed by service methods.
432
433The Context is represented by the ``` ServiceContext ``` class and has the following properties:
434
435Property | Type | Description
436-------- | ---- | -----------
437request | express.Request | The request object
438response | express.Response | The response object
439language | string | The resolved language to be used to handle the current request.
440accept | string | The preferred media type to be used to respond the current request.
441next | express.NextFunction | The next function. It can be used to delegate to the next middleware registered the processing of the current request.
442
443
444See [Types and languages](#types-and-languages) to know how the language and accept fields are calculated.
445
446The ``` @Context ``` decorator can be used on service method's arguments or on service class properties to bind
447the argument or the property to the current context object.
448
449A Context usage example:
450
451```typescript
452 @Path("context")
453 class TestService {
454 @Context
455 context: ServiceContext;
456
457 @GET
458 sayHello() {
459 switch (this.context.language) {
460 case "en":
461 return "Hello";
462 case "pt":
463 return "Olá";
464 }
465 return "Hello";
466 }
467 }
468```
469
470We can use the decorator on method arguments too:
471
472```typescript
473 @Path("context")
474 class TestService {
475
476 @GET
477 sayHello(@Context context: ServiceContext) {
478 switch (context.language) {
479 case "en":
480 return "Hello";
481 case "pt":
482 return "Olá";
483 }
484 return "Hello";
485 }
486 }
487```
488
489You can use, also, one of the other decorators to access directly one of
490the Context property. It is a kind of suggar syntax.
491
492 - @ContextRequest: To access ServiceContext.request
493 - @ContextResponse: To access ServiceContext.response
494 - @ContextNext: To access ServiceContext.next
495 - @ContextLanguage: To access ServiceContext.language
496 - @ContextAccept: To access ServiceContext.accept
497
498```typescript
499 @Path("context")
500 class TestService {
501
502 @GET
503 sayHello(@ContextLanguage language: string) {
504 switch (language) {
505 case "en":
506 return "Hello";
507 case "pt":
508 return "Olá";
509 }
510 return "Hello";
511 }
512 }
513```
514
515### Service Return
516
517This library can receive the return of your service method and handle the serialization of the response as long as
518handle the correct content type of your result and the response status codes to be sent.
519
520When a primitive type is returned by a service method, it is sent as a plain text into the response body.
521
522```typescript
523@GET
524sayHello(): string {
525 return "Hello";
526}
527```
528
529The response will contains only the String ``` Hello ``` as a plain text
530
531When an object is returned, it is sent as a json serialized string into the response body.
532
533```typescript
534@GET
535@Path(":id")
536getPerson(@PathParam(":id") id: number): Person {
537 return new Person(id);
538}
539```
540
541The response will contains the person json serialization (ex: ``` {id: 123} ```. The response
542will have a ```application/json``` context type.
543
544When the method returns nothing, an empty body is sent withh a ```204``` status code.
545
546```typescript
547@POST
548test(myObject: MyClass): void {
549 //...
550}
551```
552
553We provide also, some special types to inform that a reference to a resource is returned and
554that the server should handle it properly.
555
556Type | Description
557---- | -----------
558NewResource | Inform that a new resource was created. Server will add a Location header and set status to 201
559RequestAccepted | Inform that the request was accepted but is not completed. A Location header should inform the location where the user can monitor his request processing status. Server will set the status to 202
560MovedPermanently | Inform that the resource has permanently moved to a new location, and that future references should use a new URI with their requests. Server will set the status to 301
561MovedTemporarily | Inform that the resource has temporarily moved to another location, but that future references should still use the original URI to access the resource. Server will set the status to 302
562
563
564```typescript
565import {Return} from "typescript-rest";
566
567@Path("test")
568class TestService {
569 @POST
570 test(myObject: MyClass, @ContextRequest request: express.Request): Return.NewResource<void> {
571 //...
572 return new Return.NewResource<void>(req.url + "/" + generatedId);
573 }
574
575 @POST
576 testWithBody(myObject: MyClass, @ContextRequest request: express.Request): Return.NewResource<string> {
577 //...
578 return new Return.NewResource<string>(req.url + "/" + generatedId, 'The body of the response');
579 }
580}
581```
582
583The server will return an empty body with a ```201``` status code and a ```Location``` header pointing to
584the URL of the created resource.
585
586It is possible to specify a body to be sent in responses:
587
588```typescript
589import {Return} from "typescript-rest";
590
591interface NewObject {
592 id: string;
593}
594
595@Path("test")
596class TestService {
597 @POST
598 test(myObject: MyClass, @ContextRequest request: express.Request): Return.NewResource<NewObject> {
599 //...
600 return new Return.NewResource<NewObject>(req.url + "/" + generatedId, {id: generatedId}); //Returns a JSON on body {id: generatedId}
601 }
602}
603```
604
605
606You can use special types to download files:
607
608Type | Description
609---- | -----------
610DownloadResource | Used to reference a resource (by its fileName) and download it
611DownloadBinaryData | Used to return a file to download, based on a Buffer object
612
613For example:
614
615```typescript
616import {Return} from "typescript-rest";
617
618@Path("download")
619class TestDownload {
620 @GET
621 testDownloadFile(): Return.DownloadResource {
622 return new Return.DownloadResource(__dirname +'/test-rest.spec.js', '/test-rest.spec.js');
623 }
624
625 @GET
626 testDownloadFile(): Promise<Return.DownloadBinaryData> {
627 return new Promise<Return.DownloadBinaryData>((resolve, reject)=>{
628 fs.readFile(__dirname + '/test-rest.spec.js', (err, data)=>{
629 if (err) {
630 return reject(err);
631 }
632 return resolve(new Return.DownloadBinaryData(data, 'application/javascript', 'test-rest.spec.js'))
633 });
634 });
635 }
636}
637```
638
639
640#### Asynchronous services
641
642The above section shows how the types returned are handled by the Server. However, most of the previous examples are working
643synchronously. The recommended way is to work asynchronously, for a better performance.
644
645To work asynchronously, you can return a ```Promise``` on your service method. The above rules to handle return types
646applies to the returned promise resolved value.
647
648Some examples:
649
650```typescript
651import {Return} from "typescript-rest";
652
653@Path("async")
654class TestService {
655 @POST
656 test(myObject: MyClass, @ContextRequest request: express.Request): Promise<Return.NewResource> {
657 return new Promise<Return.NewResource>(function(resolve, reject){
658 //...
659 resolve(new Return.NewResource(req.url + "/" + generatedId));
660 });
661 }
662
663 @GET
664 testGet() {
665 return new Promise<MyClass>(function(resolve, reject){
666 //...
667 resolve(new MyClass());
668 });
669 }
670}
671```
672
673It is important to observe that you can inform your return type explicitly or not, as you can see
674in the above example.
675
676You can also use ```async``` and ```await```:
677
678```typescript
679@Path('async')
680export class MyAsyncService {
681 @GET
682 @Path('test')
683 async test( ) {
684 let result = await this.aPromiseMethod();
685 return result;
686 }
687
688 @GET
689 @Path('test2')
690 async test2( ) {
691 try {
692 let result = await this.aPromiseMethod();
693 return result;
694 } catch (e) {
695 // log error here, if you want
696 throw e;
697 }
698 }
699
700 private aPromiseMethod() {
701 return new Promise<string>((resolve, reject) => {
702 setTimeout(() => {
703 resolve('OK');
704 }, 10);
705 });
706 }
707}
708```
709
710### Errors
711
712This library provide some Error classes to map the problems that you may want to report to your clients.
713
714
715Type | Description
716---- | -----------
717BadRequestError | Used to report errors with status code 400.
718UnauthorizedError | Used to report errors with status code 401.
719ForbiddenError | Used to report errors with status code 403.
720NotFoundError | Used to report errors with status code 404.
721MethodNotAllowedError | Used to report errors with status code 405.
722NotAcceptableError | Used to report errors with status code 406.
723ConflictError | Used to report errors with status code 409.
724InternalServerError | Used to report errors with status code 500.
725NotImplementedError | Used to report errors with status code 501.
726
727If you throw any of these errors on a service method, the server you log the
728problem and send a response with the appropriate status code an the error message on its body.
729
730```typescript
731import {Errors} from "typescript-rest";
732
733@Path("async")
734class TestService {
735 @GET
736 @Path("test1")
737 testGet() {
738 return new Promise<MyClass>(function(resolve, reject){
739 //...
740 throw new Errors.NotImplementedError("This operation is not available yet");
741 });
742 }
743
744 @GET
745 @Path("test2")
746 testGet2() {
747 return new Promise<MyClass>(function(resolve, reject){
748 //...
749 reject(new Errors.NotImplementedError("This operation is not available yet"));
750 });
751 }
752
753 @GET
754 @Path("test3")
755 testGet3() {
756 throw new Errors.NotImplementedError("This operation is not available yet");
757 }
758}
759```
760
761All the three operations above will return a response with status code ```501``` and a message on the body
762```This operation is not available yet```
763
764If you want to create a custom error that report your own status code, just extend the base class ```HttpError```.
765
766
767```typescript
768import {HttpError} from "typescript-rest";
769
770class MyOwnError extends HttpError {
771 static myNoSenseStatusCode: number = 999;
772 constructor(message?: string) {
773 super("MyOwnError", MyOwnError.myNoSenseStatusCode, message);
774 }
775}
776```
777
778You must remember that all uncaught errors are handled by a expressjs [error handler](http://expressjs.com/en/guide/error-handling.html#the-default-error-handler). You could want to customize it to allow you to inform how the errors will be delivered to your users. For more on this (for those who wants, for example, to send JSON errors), take a look at [this question](https://github.com/thiagobustamante/typescript-rest/issues/16);
779
780### BodyParser Options
781
782If you need to inform any options to the body parser, you can use the @BodyOptions decorator.
783
784You can inform any property accepted by [bodyParser](https://www.npmjs.com/package/body-parser)
785
786For example:
787```typescript
788import {HttpError} from "typescript-rest";
789
790import {Errors} from "typescript-rest";
791
792@Path("async")
793class TestService {
794 @POST
795 @Path("test1")
796 @BodyOptions({limit:'100kb'})
797 testPost(myData) {
798 return new Promise<MyClass>(function(resolve, reject){
799 //...
800 throw new Errors.NotImplementedError("This operation is not available yet");
801 });
802 }
803
804 @GET
805 @Path("test2")
806 @BodyOptions({extended:false})
807 testPost2(@FormParam("field1")myParam) {
808 return new Promise<MyClass>(function(resolve, reject){
809 //...
810 reject(new Errors.NotImplementedError("This operation is not available yet"));
811 });
812 }
813
814 @GET
815 @Path("test3")
816 testGet3() {
817 throw new Errors.NotImplementedError("This operation is not available yet");
818 }
819}
820```
821
822It can be used, for example, to inform the bodyParser that it must handle date types for you:
823
824```typescript
825function dateReviver(key, value) {
826 let a;
827 if (typeof value === 'string') {
828 a = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
829 if (a) {
830 return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
831 +a[5], +a[6]));
832 }
833 }
834 return value;
835}
836
837@Path('test')
838class MyRestService {
839 @POST
840 @BodyOptions({reviver: dateReviver})
841 myHandler(param) {
842 //...
843 }
844}
845```
846
847### Types and languages
848
849It is possible to use decorators to inform the server which languages or mime types are supported by each service method.
850
851These decorators can be used on the service class or on a service method (or both).
852
853The following decorators are available:
854
855Decorator | Description
856--------- | -----------
857AcceptLanguage | Tell the [[Server]] that a class or a method should only accept requests from clients that accepts one of the supported languages.
858Accept | Tell the [[Server]] that a class or a method should only accept requests from clients that accepts one of the supported mime types.
859
860See some examples:
861
862```typescript
863@Path("test")
864@AcceptLanguage("en", "pt-BR")
865class TestAcceptService {
866 @GET
867 testLanguage(@ContextLanguage language: string): string {
868 if (language === 'en') {
869 return "accepted";
870 }
871 return "aceito";
872 }
873}
874```
875
876In the above example, we declare that only ```English``` and ```Brazilian Portuguese``` are supported.
877The order here is important. That declaration says that our first language is ```English```. So, if nothing
878was specified by the request, or if these two languages has the same weight on the
879resquest ```Accept-Language``` header, ```English``` will be the choice.
880
881If the request specifies an ```Accept-Language``` header, we will choose the language that best fit the
882header value, considering the list of possible values declared on ```@AcceptLanguage``` decorator.
883
884If none of our possibilities is good for the ```Accept-Language``` header in the request, the server
885throws a ```NotAcceptableError``` and returns a ```406``` status code for the client.
886
887You can decorate methods too, like:
888
889```typescript
890@Path("test")
891@AcceptLanguage("en", "pt-BR")
892class TestAcceptService {
893 @GET
894 @AcceptLanguage("fr")
895 testLanguage(@ContextLanguage language: string): string {
896 // ...
897 }
898}
899```
900
901On the above example, the list of accepted languages will be ```["en", "pt-BR", "fr"]```, in that order.
902
903The ```@Accept``` decorator works exaclty like ```@AcceptLanguage```, but it inform the server about the mime type
904that a service can provide. It uses the ```Accept``` header in the request to decide about the preferred media to use.
905
906```typescript
907@Path("test")
908@Accept("application/json")
909class TestAcceptService {
910 @GET
911 testType(@ContextAccept accept: string): string {
912 //...
913 }
914}
915```
916
917### IoC
918
919It is possible to delegate to [typescript-ioc](https://github.com/thiagobustamante/typescript-ioc) the instantiation of the service objects.
920
921First, install typescript-ioc:
922
923```sh
924npm install --save typescript-ioc
925```
926
927
928Then, you can configure it in two ways:
929
930 1. Create a file called ```rest.config``` and put it on the root of your project:
931
932```json
933{
934 "useIoC": true
935}
936
937```
938
939or
940
941 2. Proggramatically. Ensure that you call ```Server.useIoC()``` in the begining of your code, before any service declaration
942
943
944```typescript
945/* Ensure to call Server.useIoC() before your service declarations.
946It only need to be called once */
947Server.useIoC();
948
949@AutoWired
950class HelloService {
951 sayHello(name: string) {
952 return "Hello " + name;
953 }
954}
955
956@Path("/hello")
957@AutoWired
958class HelloRestService {
959 @Inject
960 private helloService: HelloService;
961
962 @Path(":name")
963 @GET
964 sayHello( @PathParam('name') name: string): string {
965 return this.sayHello(name);
966 }
967}
968```
969
970
971It is also possible to inform a custom serviceFactory to instantiate your services. To do this,
972call ```Server.registerServiceFactory()``` instead of ```Server.useIoC()``` and provide your own ServiceFactory implementation.
973
974You can also use the ```serviceFactory``` property in rest.config file to configure it:
975
976
977```json
978{
979 "serviceFactory": "./myServiceFactory"
980}
981```
982
983And export as default your serviceFactory class on ```./myServiceFactory.ts``` file.
984
985It could be used to allow the usage of other libraries, like [Inversify](http://inversify.io/).
986
987
988### Inheritance and abstract services
989
990It is possible to extends services like you do with normal typescript classes:
991
992```typescript
993@Path('users')
994class Users{
995 @GET
996 getUsers() {
997 return [];
998 }
999}
1000
1001@Path('superusers')
1002class SuperUsers{
1003 @GET
1004 @Path('privilegies')
1005 getPrivilegies() {
1006 return [];
1007 }
1008}
1009```
1010
1011It will expose the following endpoints:
1012
1013 - ```GET http://<my-host>/users```
1014 - ```GET http://<my-host>/superusers```
1015 - ```GET http://<my-host>/superusers/privilegies```
1016
1017**A note about abstract classes**
1018
1019A common scenario is to create an abstract class that contains some methods to be inherited by other concrete classes, like:
1020
1021```typescript
1022abstract class MyCrudService<T> {
1023
1024 @GET
1025 @Path(':id')
1026 abstract getEntity(): Promise<T>;
1027}
1028
1029@Path('users')
1030class MyUserService<User> {
1031
1032 @GET
1033 @Path(':id')
1034 async getEntity(): Promise<User> {
1035 return myUser;
1036 }
1037}
1038```
1039
1040MyCrudService, in this scenario, is a service class that contains some exposed methods (methods that are declared to be exposed as endpoints). However, the intent here is not to expose the method for MyCrudService directly (I don't want an endpoint ```GET http://<myhost>/123``` exposed). We want that only its sublclasses have the methods exposed (```GET http://<myhost>/users/123```).
1041
1042The fact that MyCrudService is an abstract class is not enough to typescript-rest library realize that its methods should not be exposed (Once it is compiled to javascript, it becomes a regular class). So you need to explicitly specify that this class should not expose any endpoint directly. It can be implemented using the ```@Abstract``` decorator:
1043
1044```typescript
1045@Abstract
1046abstract class MyCrudService<T> {
1047
1048 @GET
1049 @Path(':id')
1050 abstract getEntity(): Promise<T>;
1051}
1052
1053@Path('users')
1054class MyUserService<User> {
1055
1056 @GET
1057 @Path(':id')
1058 async getEntity(): Promise<User> {
1059 return myUser;
1060 }
1061}
1062```
1063
1064Even if MyCrudService was not a typescript abstract class, if it is decorated with ```@Abstract```, its methods will not be exposed as endpoints.
1065
1066If you don't want to use ```@Abstract```, another way to achieve the same goal is to specify which services you want to expose:
1067
1068```typescript
1069let app: express.Application = express();
1070Server.buildServices(app, MyUserService);
1071```
1072
1073or
1074
1075```typescript
1076let app: express.Application = express();
1077Server.loadServices(apis, 'lib/controllers/apis/impl/*');
1078```
1079
1080### Preprocessors
1081
1082It is possible to add a function to process the request before the handler on an endpoint by endpoint basis. This can be used to add a validator or authenticator to your application without including it in the body of the handler.
1083
1084```typescript
1085function validator(req: express.Request): express.Request {
1086 if (req.body.userId != undefined) {
1087 throw new Errors.BadRequestError("userId not present");
1088 } else {
1089 req.body.user = Users.get(req.body.userId)
1090 return req
1091 }
1092}
1093
1094@Path('users')
1095export class UserHandler {
1096
1097 @Path('email')
1098 @POST
1099 @Preprocessor(validator)
1100 setEmail(body: any) {
1101 // will have body.user
1102 }
1103}
1104```
1105
1106## Swagger
1107
1108Typescript-rest can expose an endpoint with the [swagger](http://swagger.io/) documentation for your API.
1109
1110For example:
1111
1112```typescript
1113let app: express.Application = express();
1114app.set('env', 'test');
1115Server.buildServices(app);
1116Server.swagger(app, './test/data/swagger.yaml', '/api-docs', 'localhost:5674', ['http']);
1117```
1118
1119You can provide your swagger file as an YAML or a JSON file.
1120
1121Now, just access:
1122
1123```
1124http://localhost:5674/api-docs // Show the swagger UI to allow interaction with the swagger file
1125http://localhost:5674/api-docs/json // Return the swagger.json file
1126http://localhost:5674/api-docs/yaml // Return the swagger.yaml file
1127```
1128
1129If needed, you can provide options to customize the Swagger UI:
1130
1131```typescript
1132const swaggerUiOptions = {
1133 customSiteTitle: 'My Awesome Docs',
1134 swaggerOptions: {
1135 validatorUrl: null,
1136 oauth2RedirectUrl: 'http://example.com/oauth2-redirect.html',
1137 oauth: {
1138 clientId: 'my-default-client-id'
1139 }
1140 }
1141};
1142Server.swagger(app, './swagger.yaml', '/api-docs', undefined, ['http'], swaggerUiOptions);
1143```
1144
1145> See [`swagger-ui-express`](https://github.com/scottie1984/swagger-ui-express) for more options and [`swagger-ui`](https://github.com/swagger-api/swagger-ui/blob/master/docs/usage/configuration.md) for more `swaggerOptions`. Note: Not all `swagger-ui` options are supported. Specifically, any options with a `Function` value will not work.
1146
1147To generate the swagger file, you can use the [typescript-rest-swagger](https://github.com/thiagobustamante/typescript-rest-swagger) tool.
1148
1149```sh
1150npm install typescript-rest-swagger -g
1151````
1152
1153```sh
1154swaggerGen -c ./swaggerConfig.json
1155```
1156
1157[typescript-rest-swagger](https://github.com/thiagobustamante/typescript-rest-swagger) tool can generate a swagger file as an YAML or a JSON file.
1158
1159# Breaking Changes
1160
1161Starting from version 1.0.0, it is required to inform the body type on all ReferencedResources, like:
1162
1163```typescript
1164interface NewObject {
1165 id: string;
1166}
1167
1168class TestService {
1169 @POST
1170 test(myObject: MyClass): Return.NewResource<NewObject> {
1171 //...
1172 return new Return.NewResource<NewObject>(req.url + "/" + generatedId, {id: generatedId}); //Returns a JSON on body {id: generatedId}
1173 }
1174 }
1175```
1176
1177Even when you do not provide a body on a ReferencedResouce, you need to inform ```<void>```
1178
1179```typescript
1180class TestService {
1181 @POST
1182 test(myObject: MyClass): Return.RequestAccepted<void> {
1183 //...
1184 return new Return.RequestAccepted<void>(req.url + "/" + generatedId);
1185 }
1186 }
1187```