|
1 | 1 | # speakeasy-typescript-sdk
|
2 | 2 |
|
3 |
| -## Setting up |
| 3 | + |
4 | 4 |
|
5 |
| -Feel free to use any system to manage your Node installing, but a `.nvmrc` is included in the library for `nvm` support. |
| 5 | +Speakeasy is your API Platform team as a service. Use our drop in SDK to manage all your API Operations including embeds for request logs and usage dashboards, test case generation from traffic, and understanding API drift. |
6 | 6 |
|
7 |
| -To setup: |
| 7 | +The Speakeasy Typescript SDK for evaluating API requests/responses. Compatible currently with the express framework. |
8 | 8 |
|
9 |
| -1. Install `nvm` following instructions on (nvm repo)[https://github.com/nvm-sh/nvm] |
10 |
| -2. Install the correct node version with `nvm install` |
11 |
| -3. Make sure the symlinks are correct with `nvm use` |
| 9 | +## Requirements |
12 | 10 |
|
13 |
| -## Formating, Linting & Testing |
| 11 | +Supported frameworks: |
14 | 12 |
|
15 |
| -`prettier` is used with `husky` and `lint-staged` to lint on commit, which should be installed automatically by `npm`. |
| 13 | +* express |
16 | 14 |
|
17 |
| -Use `yarn test` to run the test suite. |
| 15 | +## Usage |
18 | 16 |
|
19 |
| -Can also use `yarn test-with-debugger` to run the builtin node debugger by placing a `debugger;` anywhere in the code base. |
| 17 | +The Speakeasy Typescript SDK is hosted on NPM and can be installed with the following command: |
| 18 | + |
| 19 | +```bash |
| 20 | +npm install @speakeasy-api/speakeasy-typescript-sdk |
| 21 | +``` |
| 22 | + |
| 23 | +### Minimum configuration |
| 24 | + |
| 25 | +[Sign up for free on our platform](https://www.speakeasyapi.dev/). After you've created a workspace and generated an API key enable Speakeasy in your API as follows: |
| 26 | + |
| 27 | +#### Express |
| 28 | + |
| 29 | +Configure Speakeasy in your middleware chain as follows: |
| 30 | + |
| 31 | +```typescript |
| 32 | +import speakeasy, { Config } from "@speakeasy-api/speakeasy-typescript-sdk"; |
| 33 | +import express from "express"; |
| 34 | + |
| 35 | +const app = express(); |
| 36 | + |
| 37 | +// Configure the global speakeasy SDK instance |
| 38 | +const cfg: Config = { |
| 39 | + apiKey: "YOUR API KEY HERE", // retrieve from Speakeasy API dashboard. |
| 40 | + apiID: "YOUR API ID HERE", // custom Api ID to associate captured requests with. |
| 41 | + versionID: "YOUR VERSION ID HERE", // custom Version ID to associate captured requests |
| 42 | + port: 3000, // The port number your express app is listening on (required to build full URLs on non-standard ports) |
| 43 | +}; |
| 44 | +speakeasy.configure(cfg); |
| 45 | + |
| 46 | +// Add the speakeasy middleware to your express app |
| 47 | +app.use(speakeasy.expressMiddleware()); |
| 48 | + |
| 49 | +// Rest of your express app setup code |
| 50 | +``` |
| 51 | + |
| 52 | +#### NestJS |
| 53 | + |
| 54 | +Configure Speakeasy in your NestJS app as follows: |
| 55 | + |
| 56 | +```typescript |
| 57 | +import speakeasy, { Config } from '@speakeasy-api/speakeasy-typescript-sdk'; |
| 58 | +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; |
| 59 | + |
| 60 | +import { AppController } from './app.controller'; |
| 61 | +import { AppService } from './app.service'; |
| 62 | + |
| 63 | +@Module({ |
| 64 | + imports: [], |
| 65 | + controllers: [AppController], |
| 66 | + providers: [AppService], |
| 67 | +}) |
| 68 | +export class AppModule implements NestModule { |
| 69 | + configure(consumer: MiddlewareConsumer) { |
| 70 | + // Configure the global speakeasy SDK instance |
| 71 | + const cfg: Config = { |
| 72 | + apiKey: "YOUR API KEY HERE", // retrieve from Speakeasy API dashboard. |
| 73 | + apiID: "YOUR API ID HERE", // custom Api ID to associate captured requests with. |
| 74 | + versionID: "YOUR VERSION ID HERE", // custom Version ID to associate captured requests |
| 75 | + port: 3000, // The port number your express app is listening on (required to build full URLs on non-standard ports) |
| 76 | + }; |
| 77 | + speakeasy.configure(cfg); |
| 78 | + |
| 79 | + // Configure the NestJS middleware for the routes of you Controller |
| 80 | + consumer.apply(speakeasy.nestJSMiddleware()).forRoutes(AppController); |
| 81 | + } |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +Build and deploy your app and that's it. Your API is being tracked in the Speakeasy workspace you just created |
| 86 | +and will be visible on the dashboard next time you log in. Visit our [docs site](https://docs.speakeasyapi.dev/) to |
| 87 | +learn more. |
| 88 | + |
| 89 | + |
| 90 | +### Advanced configuration |
| 91 | + |
| 92 | +The Speakeasy SDK provides both a global and per Api configuration option. If you want to use the SDK to track multiple Apis or Versions from the same service you can configure individual instances of the SDK. |
| 93 | + |
| 94 | +#### Express |
| 95 | + |
| 96 | +```typescript |
| 97 | +import { Config, SpeakeasySDK } from "@speakeasy-api/speakeasy-typescript-sdk"; |
| 98 | +import express from "express"; |
| 99 | + |
| 100 | +const app = express(); |
| 101 | + |
| 102 | +// Configure a speakeasy SDK instance |
| 103 | +const cfg: Config = { |
| 104 | + apiKey: "YOUR API KEY HERE", // retrieve from Speakeasy API dashboard. |
| 105 | + apiID: "YOUR API ID HERE", // custom Api ID to associate captured requests with. |
| 106 | + versionID: "YOUR VERSION ID HERE", // custom Version ID to associate captured requests |
| 107 | + port: 3000, // The port number your express app is listening on (required to build full URLs on non-standard ports) |
| 108 | +}; |
| 109 | +const sdk = new SpeakeasySDK(cfg); |
| 110 | + |
| 111 | +// Add the speakeasy middleware to your express app/router |
| 112 | +app.use(sdk.expressMiddleware()); |
| 113 | + |
| 114 | +// Rest of your express app setup code |
| 115 | +``` |
| 116 | + |
| 117 | +##### NestJS |
| 118 | + |
| 119 | +```typescript |
| 120 | +import { Config, SpeakeasySDK } from '@speakeasy-api/speakeasy-typescript-sdk'; |
| 121 | +import { MiddlewareConsumer, Module, NestModule } from '@nestjs/common'; |
| 122 | + |
| 123 | +import { AppController } from './app.controller'; |
| 124 | +import { AppService } from './app.service'; |
| 125 | + |
| 126 | +@Module({ |
| 127 | + imports: [], |
| 128 | + controllers: [AppController], |
| 129 | + providers: [AppService], |
| 130 | +}) |
| 131 | +export class AppModule implements NestModule { |
| 132 | + configure(consumer: MiddlewareConsumer) { |
| 133 | + // Configure a speakeasy SDK instance |
| 134 | + const cfg: Config = { |
| 135 | + apiKey: "YOUR API KEY HERE", // retrieve from Speakeasy API dashboard. |
| 136 | + apiID: "YOUR API ID HERE", // custom Api ID to associate captured requests with. |
| 137 | + versionID: "YOUR VERSION ID HERE", // custom Version ID to associate captured requests |
| 138 | + port: 3000, // The port number your express app is listening on (required to build full URLs on non-standard ports) |
| 139 | + }; |
| 140 | + const sdk = new SpeakeasySDK(cfg); |
| 141 | + |
| 142 | + // Configure the NestJS middleware for the routes of you Controller |
| 143 | + consumer.apply(sdk.nestJSMiddleware()).forRoutes(AppController); |
| 144 | + } |
| 145 | +} |
| 146 | +``` |
| 147 | + |
| 148 | +This allows multiple instances of the SDK to be associated with different routers or routes within your service. |
| 149 | + |
| 150 | +### On-Premise Configuration |
| 151 | + |
| 152 | +The SDK provides a way to redirect the requests it captures to an on-premise deployment of the Speakeasy Platform. This is done through the use of environment variables listed below. These are to be set in the environment of your services that have integrated the SDK: |
| 153 | + |
| 154 | +* `SPEAKEASY_SERVER_URL` - The url of the on-premise Speakeasy Platform's GRPC Endpoint. By default this is `grpc.prod.speakeasyapi.dev:443`. |
| 155 | +* `SPEAKEASY_SERVER_SECURE` - Whether or not to use TLS for the on-premise Speakeasy Platform. By default this is `true` set to `SPEAKEASY_SERVER_SECURE="false"` if you are using an insecure connection. |
| 156 | + |
| 157 | +## Request Matching |
| 158 | + |
| 159 | +The Speakeasy SDK out of the box will do its best to match requests to your provided OpenAPI Schema. It does this by extracting the path template used by one of the supported frameworks above for each request captured and attempting to match it to the paths defined in the OpenAPI Schema, for example: |
| 160 | + |
| 161 | +### Express |
| 162 | + |
| 163 | +```typescript |
| 164 | +const app = express(); |
| 165 | +app.use(speakeasy.expressMiddleware()); |
| 166 | +app.all("/v1/user/:id/action/:action", myHandler); // The path template "/v1/user/{id}/action/{action}" is captured automatically by the SDK after being normalized to the OpenAPI spec format for paths. |
| 167 | +``` |
| 168 | + |
| 169 | +### NestJS |
| 170 | + |
| 171 | +```typescript |
| 172 | +@Controller() |
| 173 | +export class AppController { |
| 174 | + constructor(private readonly appService: AppService) {} |
| 175 | + |
| 176 | + @Get('/v1/user/:id/action/:action') // The path template "/v1/user/{id}/action/{action}" is captured automatically by the SDK after being normalized to the OpenAPI spec format for paths. |
| 177 | + myHandler(): string { |
| 178 | + // handler code here |
| 179 | + } |
| 180 | +} |
| 181 | +``` |
| 182 | + |
| 183 | +This isn't always successful or even possible, meaning requests received by Speakeasy will be marked as `unknown`, and potentially not associated with your Api, Version or ApiEndpoints in the Speakeasy Dashboard. |
| 184 | + |
| 185 | +This normally happens if your path contains regex patterns or is a catch all path and your handler parses the routes manually. |
| 186 | + |
| 187 | +To help the SDK in these situations you can provide path hints per request that match the paths in your OpenAPI Schema: |
| 188 | + |
| 189 | +```typescript |
| 190 | +const app = express(); |
| 191 | +app.use(speakeasy.expressMiddleware()); |
| 192 | +app.all("/", (req, res) => { |
| 193 | + // Provide a path hint for the request using the OpenAPI Path Templating format: https://swagger.io/specification/#path-templating-matching |
| 194 | + req.controller.setPathHint("/v1/user/{id}/action/{action}"); |
| 195 | + |
| 196 | + // the rest of your handlers code |
| 197 | +}); |
| 198 | +``` |
| 199 | +The above example will work for NestJS as well, just get the Speakeasy MiddlewareController from the request object. |
| 200 | + |
| 201 | +NOTE: If using nested Routers in express or Controller path prefixs in NestJS the SpeakeasySDK will not be able to get the full path template for the request due to a [current issue](https://github.com/expressjs/express/issues/2879) in express. To work around this you can manually set path hints as above or we can monkey patch in a modification to express to enable the SpeakeasySDK to get the full path template: |
| 202 | + |
| 203 | +```typescript |
| 204 | +/* eslint-disable */ |
| 205 | +// @ts-nocheck |
| 206 | +import express from "express"; |
| 207 | + |
| 208 | +/* Credit to @watson and @jagadish-kb https://github.com/expressjs/express/issues/2879#issuecomment-269433170 */ |
| 209 | + |
| 210 | +const origUse = express.Router.use; |
| 211 | + |
| 212 | +express.Router.use = function (fn) { |
| 213 | + if (typeof fn === "string" && Array.isArray(this.stack)) { |
| 214 | + let offset = this.stack.length; |
| 215 | + const result = origUse.apply(this, arguments); |
| 216 | + let layer; |
| 217 | + for (; offset < this.stack.length; offset++) { |
| 218 | + layer = this.stack[offset]; |
| 219 | + // I'm not sure if my check for `fast_slash` is the way to go here |
| 220 | + // But if I don't check for it, each stack element will add a slash to the path |
| 221 | + if (layer && layer.regexp && !layer.regexp.fast_slash) |
| 222 | + layer.__mountpath = fn; |
| 223 | + } |
| 224 | + return result; |
| 225 | + } else { |
| 226 | + return origUse.apply(this, arguments); |
| 227 | + } |
| 228 | +}; |
| 229 | + |
| 230 | +var origPP = express.Router.process_params; |
| 231 | + |
| 232 | +express.Router.process_params = function (layer, called, req, res, done) { |
| 233 | + const path = |
| 234 | + (req.route && |
| 235 | + (req.route.path || (req.route.regexp && req.route.regexp.source))) || |
| 236 | + layer.__mountpath || |
| 237 | + ""; |
| 238 | + if (req.__route && path) { |
| 239 | + const searchFromIdx = req.__route.length - path.length; |
| 240 | + if (req.__route.indexOf(path, searchFromIdx) > 0) { |
| 241 | + return origPP.apply(this, arguments); |
| 242 | + } |
| 243 | + } |
| 244 | + req.__route = (req.__route || "") + path; |
| 245 | + |
| 246 | + return origPP.apply(this, arguments); |
| 247 | +}; |
| 248 | +``` |
| 249 | + |
| 250 | +Create a file called `expressmonkeypatch.ts` or similar and import it into your service's `main.ts` file `import "./expressmonkeypatch";`. This will path express and allow the SDK to determine the full path automatically. |
| 251 | + |
| 252 | +## Capturing Customer IDs |
| 253 | + |
| 254 | +To help associate requests with customers/users of your APIs you can provide a customer ID per request handler: |
| 255 | + |
| 256 | + |
| 257 | + |
| 258 | +```typescript |
| 259 | +const app = express(); |
| 260 | +app.use(speakeasy.expressMiddleware()); |
| 261 | +app.all("/", (req, res) => { |
| 262 | + // Provide a path hint for the request using the OpenAPI Path Templating format: https://swagger.io/specification/#path-templating-matching |
| 263 | + req.controller.setCustomerID("a-customers-id"); // This customer ID will be used to associate this instance of a request with your customers/users |
| 264 | + |
| 265 | + // the rest of your handlers code |
| 266 | +}); |
| 267 | +``` |
| 268 | + |
| 269 | +Note: This is not required, but is highly recommended. By setting a customer ID you can easily associate requests with your customers/users in the Speakeasy Dashboard, powering filters in the Request Viewer [(Coming soon)](https://docs.speakeasyapi.dev/speakeasy-user-guide/request-viewer-coming-soon). |
0 commit comments