@servicestack/client

This library contains the Typed ServiceStack Client library that is an idiomatic port of ServiceStack's
ss-utils.js JavaScript Client
in native TypeScript. It provides integration with many ServiceStack features
including TypeScript Add ServiceStack Reference
and Server Events.
Web, Node.js and React Native
It contains a clean "jQuery-free" implementation based on JavaScript's new
Fetch API standard,
utilizing the fetch-everywhere implementation
so it can be used in both JavaScript Web apps, Node.js server and test projects as well as
React Native iOS and Android Mobile Apps as seen in TypeScript Server Event Examples.
Install
This package is pre-configured in all ServiceStackVS TypeScript VS.NET Templates or can be installed with:
npm install @servicestack/client
v1.x Changes
- new
api
and apiVoid
APIs return an ApiResult<TResponse>
encapsulating both response
and error
- old
send
API renamed to fetch
- new
send
and sendVoid
APIs can infer IVerb Interface Markers
New API
The new api
returns a typed ApiResult<Response>
Value Result that encapsulates either a Typed Response or a
structured API Error populated in ResponseStatus
allowing you to handle API responses programmatically without
try/catch
handling:
const api = client.api(new Hello({ name }))
if (api.failed) {
console.log(`Greeting failed! ${e.errorCode}: ${e.errorMessage}`);
return;
}
console.log(`API Says: ${api.response.result}`)
Simplified API Handling
Being able to treat errors as values greatly increases the ability to programmatically handle and genericise api handling
and greatly simplifies functionality needing to handle both successful and error responses like binding to UI components.
An example of this is below where we're able to concurrently fire off multiple unrelated async requests in parallel,
wait for them all to complete, print out the ones that have succeeded or failed then access their strong typed responses:
import { JsonServiceClient } from "@servicestack/client"
let requests:ApiRequest[] = [
new AppOverview(),
new DeleteTechnology(),
new GetAllTechnologies(),
new GetAllTechnologyStacks(),
]
let results = await Promise.all(requests.map(async (request) =>
({ request, api:await client.api(request) as ApiResponse}) ))
let failed = results.filter(x => x.api.failed)
console.log(`${failed.length} failed:`)
failed.forEach(x =>
console.log(` ${x.request.getTypeName()} Request Failed: ${failed.map(x => x.api.errorMessage)}`))
let succeeded = results.filter(x => x.api.succeeded)
console.log(`\n${succeeded.length} succeeded: ${succeeded.map(x => x.request.getTypeName()).join(', ')}`)
let r = succeeded.find(x => x.request.getTypeName() == 'AppOverview')?.api.response as AppOverviewResponse
if (r) console.log(`Top 5 Technologies: ${r.topTechnologies.slice(0,4).map(tech => tech.name).join(', ')}`)
Output:
1 failed
DeleteTechnology Request Failed: Unauthorized
3 succeeded: AppOverview, GetAllTechnologies, GetAllTechnologyStacks
Top 5 Technologies: Redis, MySQL, Amazon EC2, Nginx
Being able to treat Errors as values has dramatically reduced the effort required to accomplish the same feat if needing
to handle errors with try/catch
.
Requires ServiceStack v5.13+
Ideal Typed Message-based API
The TypeScript JsonServiceClient
enables the same productive, typed API development experience available
in ServiceStack's other 1st-class supported client platforms.
The JsonServiceClient
leverages the additional type hints ServiceStack embeds in each TypeScript Request DTO
to achieve the ideal typed, message-based API - so all API requests benefit from a succinct, boilerplate-free
Typed API.
Here's a quick look at what it looks like. The example below shows how to create a
C# Gist in Gistlyn
after adding a TypeScript ServiceStack Reference
to gistlyn.com
and installing the @servicestack/client
npm package:
import { JsonServiceClient } from '@servicestack/client';
import { StoreGist, GithubFile } from './dtos';
const client = new JsonServiceClient("https://gistlyn.com");
const request = new StoreGist({
files: {
[file.filename]: new GithubFile({
filename: 'main.cs',
content: 'var greeting = "Hi, from TypeScript!";'
})
}
})
const api = client.api(request);
if (api.succeeded) {
console.log(`New C# Gist was created with id: ${r.gist}`);
location.href = `https://gist.cafe/${r.gist}`;
} else {
console.log("Failed to create Gist: ", e.errorMessage);
}
Where the response
param is typed to StoreGistResponse
DTO Type.
Sending additional arguments with Typed API Requests
Many AutoQuery Services utilize
implicit conventions
to query fields that aren't explicitly defined on AutoQuery Request DTOs, these can now be queried by specifying additional arguments with the typed Request DTO, e.g:
const response = await client.get(new FindTechStacks(), { VendorName: "ServiceStack" });
Which will return TechStacks developed by ServiceStack.
Calling APIs with Custom URLs
You can call Services using relative or absolute urls, e.g:
client.get<GetTechnologyResponse>("/technology/ServiceStack")
client.get<GetTechnologyResponse>("http://techstacks.io/technology/ServiceStack")
client.get<GetTechnologyResponse>("/technology", { Slug: "ServiceStack" })
as well as POST Request DTOs to custom urls:
client.postToUrl("/custom-path", request, { Slug: "ServiceStack" });
client.putToUrl("http://example.org/custom-path", request);
Raw Data Responses
The JsonServiceClient
also supports Raw Data responses like string
and byte[]
which also get a Typed API
once declared on Request DTOs using the IReturn<T>
marker:
public class ReturnString : IReturn<string> {}
public class ReturnBytes : IReturn<byte[]> {}
Which can then be accessed as normal, with their Response typed to a JavaScript string
or Uint8Array
for
raw byte[]
responses:
let str:string = await client.get(new ReturnString());
let data:Uint8Array = await client.get(new ReturnBytes());
Authenticating using Basic Auth
Basic Auth support is implemented in JsonServiceClient
and follows the same API made available in the C#
Service Clients where the userName/password
properties can be set individually, e.g:
var client = new JsonServiceClient(baseUrl);
client.userName = user;
client.password = pass;
const response = await client.get(new SecureRequest());
Or use client.setCredentials()
to have them set both together.
Authenticating using Credentials
Alternatively you can authenticate using userName/password credentials by
adding a TypeScript Reference
to your remote ServiceStack Instance and sending a populated Authenticate
Request DTO, e.g:
const response = await client.post(new Authenticate({
provider: "credentials", userName, password, rememberMe: true }));
This will populate the JsonServiceClient
with
Session Cookies
which will transparently be sent on subsequent requests to make authenticated requests.
Authenticating using JWT
Use the bearerToken
property to Authenticate with a ServiceStack JWT Provider using a JWT Token:
client.bearerToken = jwtToken;
Alternatively you can use a Refresh Token instead:
client.refreshToken = refreshToken;
Authenticating using an API Key
Use the bearerToken
property to Authenticate with an API Key:
client.bearerToken = apiKey;
Transparently handle 401 Unauthorized Responses
If the server returns a 401 Unauthorized Response either because the client was Unauthenticated or the
configured Bearer Token or API Key used had expired or was invalidated, you can use onAuthenticationRequired
callback to re-configure the client before automatically retrying the original request, e.g:
client.onAuthenticationRequired = async () => {
const authClient = new JsonServiceClient(authBaseUrl);
authClient.userName = userName;
authClient.password = password;
const response = await authClient.get(new Authenticate());
client.bearerToken = response.bearerToken;
};
var response = await client.get(new Secured());
Automatically refresh Access Tokens
With the Refresh Token support in JWT
you can use the refreshToken
property to instruct the Service Client to automatically fetch new
JWT Tokens behind the scenes before automatically retrying failed requests due to invalid or expired JWTs, e.g:
const authClient = new JsonServiceClient(authBaseUrl);
authClient.userName = userName;
authClient.password = password;
const authResponse = await authClient.get(new Authenticate());
client.refreshToken = authResponse.RefreshToken;
const response = await client.get(new Secured());
Use the refreshTokenUri
property when refresh tokens need to be sent to a different ServiceStack Server, e.g:
client.refreshToken = refreshToken;
client.refreshTokenUri = authBaseUrl + "/access-token";
The TypeScript ServerEventClient
is an idiomatic port of ServiceStack's
C# Server Events Client
in native TypeScript providing a productive client to consume ServiceStack's
real-time Server Events that can be used in TypeScript
Web, Node.js Server and React Native iOS and Android Mobile Apps.
const channels = ["home"];
const client = new ServerEventsClient("/", channels, {
handlers: {
onConnect: (sub:ServerEventConnect) => {
console.log("You've connected! welcome " + sub.displayName);
},
onJoin: (msg:ServerEventJoin) => {
console.log("Welcome, " + msg.displayName);
},
onLeave: (msg:ServerEventLeave) => {
console.log(msg.displayName + " has left the building");
},
onUpdate: (msg:ServerEventUpdate) => {
console.log(msg.displayName + " channels subscription were updated");
},
onMessage: (msg:ServerEventMessage) => {},
announce: (text:string) => {},
chat: (chatMsg:ChatMessage) => {},
CustomMessage: (msg:CustomMessage) => {},
},
receivers: {
tv: {
watch: function (id) {
var el = document.querySelector("#tv");
if (id.indexOf('youtu.be') >= 0) {
var v = splitOnLast(id, '/')[1];
el.innerHTML = templates.youtube.replace("{id}", v);
} else {
el.innerHTML = templates.generic.replace("{id}", id);
}
el.style.display = 'block';
},
off: function () {
var el = document.querySelector("#tv");
el.style.display = 'none';
el.innerHTML = '';
}
}
},
onException: (e:Error) => {},
onReconnect: (e:Error) => {}
})
.addListener("theEvent",(e:ServerEventMessage) => {})
.start();
When publishing a DTO Type for your Server Events message, your clients will be able to benefit from the generated DTOs in TypeScript ServiceStack References.
Feedback and Support
Support is available from the ServiceStack Customer Forums
or StackOverflow using the [servicestack]
tag.
Any reproducible issues can be submitted to ServiceStack's Issue Tracker.
Troubleshooting
If you're getting missing Type Definitions for Headers
, Response
, Request
, etc. You'll need to import
the Type Definitions for W3C's fetch
API, preferably by using the latest version of TypeScript and
referencing the core es2016 and dom libs in TypeScript's tsconfig.json
, e.g:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": [ "es2015", "dom" ]
}
}
Alternatively you can import the whatwg-fetch Type Definitions with:
npm install @types/whatwg-fetch --save-dev