Skip to content

Repository files navigation

Grappa

Decorator-powered REST client for Angular 13+ and its HttpClient, plus RxJs 6+.

Last versionAngular VersionsNode
17.0.013 up to 17 (included)18
16.0.013 up to 16 (included)16
1.1.113 up to 15 (included)14

🛠 Installation

  • With npm: npm i --save @elemental-concept/grappa

Add GrappaModule to your main AppModule to imports section.

@NgModule({declarations: [ ... ],imports: [
...,GrappaModule],providers: [],bootstrap: [ ... ]})exportclassAppModule{}

📖 Introduction

Grappa minimises boilerplate code required for REST clients and streamlines request and response modifications with filters. Simply define a list of methods which reflect REST API:

@Injectable()
@RestClient('http://example.com/api/')exportclassUserService{
@GET('/users')list: ()=>Observable<User[]>;
@GET('/users/{0}')find: (id: number)=>Observable<User>;
@PATCH('/users/{0}')update: (id: number,user: User)=>Observable<User>;
@POST('/users')create: (user: User)=>Observable<User>;
@PUT('/users/{0}')update: (id: number,user: User)=>Observable<User>;}

Grappa will auto-generate required class methods which can be easily called from any component:

@Component({// ...})exportclassAppComponent{constructor(privateuserService: UserService){userService.find(42).subscribe(user=>console.log(user));}}

Define @BeforeRequest() filter methods to uniformly modify data being sent to the API. A good example could be JWT injection, but we are covering that with a separate library Grappa JWT

Custom header injection is a good example:

@Injectable()
@RestClient('http://example.com/api/')exportclassUserService{// ...constructor(privateauthService: AuthService){}
@BeforeRequest()privatecustomHeaders(request: RestRequest){request.headers={
...request.headers,'X-Xid': id,'X-Client-Id': clientId,'X-User-Id': userId,};}}

Use @AfterRequest() to transform responses and inject global error handlers:

@Injectable()
@RestClient('http://example.com/api/')exportclassUserService{// ...
@AfterRequest()tranformResponse(response: Observable<HttpResponse<any>>){returnresponse.map(item=>User.fromResponse(item.body));}
@AfterRequest()handleErrors(response: Observable<HttpResponse<any>>){returnresponse.catch(error=>{alert('Error!');returnObservable.of(error);});}}

API

Decorators on a class and its properties define how a request will be handled.

@RestClient(baseUrl: string = '')

Optional decorator which allows to define base URL for all REST methods in a class. If decorator is not present or baseUrl argument is empty string, null or undefined, then it is assumed that property decorators will contain full URLs.

Example with @RestClient:

@Injectable()
@RestClient('http://example.com/api/')exportclassUserService{
@GET('/users')list: ()=>Observable<User[]>;// List method will call http://example.com/api/users}

Example without @RestClient:

@Injectable()exportclassUserService{
@GET('http://somedomain.org/users')list: ()=>Observable<User[]>;// List method will call http://somedomain.org/users}

@GET(endpoint: string, options: RequestOptions = {})

Makes HTTP GET request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Example:

@GET('/users/{0}/posts?page={1}')
getUserPosts: (userId: number,page: number)=>Observable<Post[]>;

{0} will be replaced with userId value and {1} will be replaced with page value.


@PATCH(endpoint: string, options: RequestOptions = {})

Makes HTTP PATCH request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a PATCH body.

@PATCH('/users/{0}',options: RequestOptions={})
update: (userId: number,user: User)=>Observable<User>;

@POST(endpoint: string, options: RequestOptions = {})

Makes HTTP POST request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a POST body.

@POST('/users')
create: (user: User)=>Observable<User>;

@PUT(endpoint: string, options: RequestOptions = {})

Makes HTTP PUT request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Last function argument will be used as a PUT body.

@PUT('/users/{0}',options: RequestOptions={})
update: (userId: number.user: User)=>Observable<User>;

@DELETE(endpoint: string, options: RequestOptions = {})

Makes HTTP DELETE request to the specified end-point. Arguments passed to the decorated function can be inserted into end-point URL using index based templates. Indices start at 0. Example:

@DELETE('/users/{0}')
remove: (userId: number)=>Observable<User>;

@BeforeRequest(applyTo: OptionalList<string> = null)

Runs decorated method before every request in a class.

@BeforeRequest()beforeFilter(request: RestRequest){request.headers['X-Dummy']='Abcde';}

@AfterRequest(applyTo: OptionalList<string> = null)

Runs decorated method after every request in a class.

@AfterRequest()afterFilter(response: Observable<HttpResponse<any>>){returnresponse.map(r=>r.body.value);}

RequestOptions

Configuration for specific GET, POST, PATCH, PUT and DELETE requests.

exportinterfaceRequestOptions{observe?: ObserveOptions;query?: number|boolean;emptyBody?: boolean;}exportenumObserveOptions{Body='body',Response='response'}

Given

@GET('/users/{0}/posts?page={1}')
getUserPosts: (userId: number,page: number)=>Observable<Post[]>;

you'll have {1} as the page attribute, but if instead you have multiple query params, the best approach is to have a single object and then use the query option:

@GET('/users/{0}/posts',{query: true})
getUserPosts: (userId: number,{page: number,search: string,sort: string,hideOutOfStock: boolean})=>Observable<Post[]>;

This way Grappa will translate the object into a list of query params, like this:

`/users/{0}/posts?page=${page}&search=${search}&sort=${sort}&hideOutOfStock=${hideOutOfStock}`

If for any reason you need to send a PUT or a POST without a body (which is not a good practise), we added a new flag emptyBody, that allow that. So you could send something like this:

@PUT('/users/{0}',{emptyBody: true})
getUserPosts: (userId)=>Observable<Post[]>;

If for any reason you need to send a PUT or a POST url params as well as query params as well body object, you need to specify which arg index is the query params

@PUT('/users/{0}',{query: 1})
getUserPosts: (userId,queryParams: {name: string},body: User)=>Observable<Post[]>;

OptionalList

Allows to specify the names of request methods specific filter should apply.

exporttypeOptionalList<T>=T|T[]|null;

Usage:

@GET('/users')
get: ()=>Observable<User[]>;
@POST('/users')
create: (user: User)=>Observable<User>;
@BeforeRequest('create')beforeFilter(request: RestRequest){// This filter function will only apply to create() calls}

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

2 watching

Forks

Releases

Packages

Used by

Contributors

Languages