UNPKG

12.7 kBMarkdownView Raw
1# apilove
2
3(still a work in progress)
4
5A no-nonsense framework for creating server(less) APIs in TypeScript — api♥︎
6
7## features
8
9### decorators
10TypeScript decorators make building routes, parameters, and validation a breeze (don't worry, you still have access to all of the underlying raw express.js req, res objects if you want them).
11
12```typescript
13export class SampleAPI {
14
15 @APIEndpoint({path: "/foo"})
16 fooX(@APIParameter({default: "bar"}) what: string, req?, res?): Promise<any> {
17
18 return new Promise<any>((resolve, reject) => {
19 resolve(`foo ${what}`);
20 });
21
22 }
23}
24```
25
26### server(less)
27Your API code will run seamlessly both as an API Gateway/Lambda combo or a standard express.js server— apilove automatically determines the environment it's running in and configures itself accordingly.
28
29```typescript
30// This is the only code that is required to run your API.
31// When running on Lambda, this acts as your handler to an API Gateway proxy request.
32// When running on a server, this acts as your express.js server.
33module.exports.handler = APILove.start({
34 apis:[
35 {
36 require: "./SampleAPI"
37 }
38 ]
39});
40```
41
42### lazy loading
43APIs can be split into smaller chunks and loaded on demand— this can cut down drastically on memory requirements for Lambda functions by only loading the parts of your API that are needed for a given request.
44
45### endpoints are functions
46If you use the supplied TypeScript decorators your API handler functions look just like standard functions (because they are).
47
48```typescript
49// Useful for testing and/or using your API like a library without the overhead of a web server.
50let sapi = new SampleAPI();
51sapi.fooX("bar")
52 .then((data) => {
53 console.log(data);
54 }).catch((error) => {
55 console.error(error);
56 });
57```
58
59### deploy-joy
60We include a sample serverless.yml configuration which allows you to fully deploy your API to AWS with [serverless](https://serverless.com/) in a few keystrokes.
61
62`> serverless deploy`
63
64### handy services
65We include some standard service libraries that make building APIs even easier:
66
67- Key-Value Storage: A generic key-value storage service (with TTL) that will store values in memory or disk while in development, but can/will automatically switch to DynamoDB when deployed to AWS.
68- File Storage: A generic file storage service that will allow you store files on local disk or on S3 with the flip of an environment variable
69
70## docs
71
72### building your api
73An apilove API is simply any TypeScript class with decorated methods that return a Promise.
74
75```typescript
76import {APIEndpoint, APIParameter} from "apilove";
77
78export class SampleAPI {
79
80 @APIEndpoint({path: "/foo"})
81 simple(what: string): Promise<any> {
82
83 return new Promise<any>((resolve, reject) => {
84 resolve(`foo ${what}`);
85 });
86 }
87}
88```
89
90In the above example, apilove will call this method any time a GET request is made to your API at `http://myapi.com/foo`. It will also expect a parameter called `what` to be sent in some form or another (more on parameters later).
91
92#### @APIEndpoint decorator
93```typescript
94@APIEndpoint(options: APIEndpointOptions)
95
96interface APIEndpointOptions {
97 // The method to be used when requesting this endpoint. Defaults to "get".
98 method?: string;
99
100 // The path to reach this endpoint. Defaults to "/".
101 path?: string;
102
103 // Any express.js middleware functions you want to be executed before invoking this method. Useful for things like authentication.
104 middleware?: ((req, res, next?) => void)[] | ((req, res, next) => void);
105
106 // Turn this on if you want to return data as-is and not in HAPI format
107 disableFriendlyResponse?:boolean;
108
109 // Specify a function here to handle the response yourself
110 successResponse?: (responseData:any, res) => void;
111
112 // If set to true, a valid JWT must be present in the request, otherwise a 401 error will be thrown
113 requireAuthentication?:boolean;
114}
115```
116
117### api parameters
118apilove has powerful automatic validation and type conversion capabilities so you never have to worry (or worry less) about parsing and validating data coming to your API.
119
120Here is a little bit about how api parameters are mapped to your method:
121
1221. apilove scans the names and type declarations in your method and looks for a corresponding value in the incoming API request.
1231. The incoming API request is scanned for a parameter to match your method parameter in the following order:
124 1. A path parameter, like `/foo/:what` with the same name
125 1. A query parameter, like `?what=bar` wtih the same name
126 1. A body parameter specified in either JSON or form values
127 1. A cookie with the same name
128 1. A header with the same name
1291. By default, if no value can be found an error is automatically returned to the API caller and the method is not called.
1301. If a value is found, apilove will try its best to convert it to the proper type you've declared. If it can't be converted to the proper type, a validation error will be returned to the API caller.
1311. If the all of the parameters are found and properly validated, apilove will call your method just like normal.
1321. All of this functionality can be modified with an `@APIParameter` decorator on your method property.
133
134For example:
135```typescript
136@APIEndpoint({
137 method: "POST",
138 path: "/foo/:what"
139})
140fooX(
141 what: string, // This will be retrieved as a string from the URL
142 @APIParameter({sources: "body"}) data // The body will be parsed and sent back here
143): Promise<any> {
144
145 return new Promise<any>((resolve, reject) => {
146 resolve(`foo ${what} with some ${data}`);
147 });
148
149}
150```
151
152#### @APIParameter decorator
153```typescript
154@APIParameter(options: APIParameterOptions)
155
156interface APIParameterOptions {
157
158 /**
159 * If set to true, an error will not be thrown to the API caller if the value is not sent
160 */
161 optional?: boolean;
162
163 /**
164 * A default value to be used if one can't be found. This would be an equivalent shortcut for setting optional=true and providing a default value for your method property
165 */
166 defaultValue?: any;
167
168 /**
169 * A synchronous function that can be used to transform an incoming parameter into something else. Can also be used as validation by throwing an error.
170 * You also get access to the raw express.js req object if you want it.
171 */
172 processor?: (value: any, req?) => any;
173
174 /**
175 * One or more sources from which to look for this value. This is basically a path in the req object. So for example, a value of `query` would be equivalent to `req.query[myParamName]`
176 * Multiple values can be defined, and whichever one results in a non-null value first will be used. Defaults to ["params", "query", "body", "cookie", "headers"].
177 */
178 sources?: string[] | string;
179
180 /**
181 * If set to true, the entire source will be returned instead of looking for a particular value. Defaults to false.
182 *
183 * Examples:
184 *
185 * The following would look for something named `userData` in the query params and return that.
186 * @APIParameter({sources:["query"]})
187 * userData:string
188 *
189 * The following would take all the query params and return them as an object
190 * @APIParameter({sources:["query"], includeFullSource:true})
191 * userData:{[paramName:string] : any}
192 */
193 includeFullSource?: boolean;
194
195 /**
196 * This is the raw name of the parameter to look for in cases where the name can't be represented as a valid javascript variable name.
197 * Examples usages might be when looking for a header like "content-type" or a parameter named "function"
198 */
199 rawName?: string;
200}
201```
202
203#### redirects
204If you'd like to redirect the response, simply pass a `URL` object back to the `resolve` callback.
205
206#### accessing the raw express.js request and response
207While it's generally discouraged (because it may break the ability to call your method like a regular function), you can also gain access to the raw request and response objects by appending them to your method parameters.
208
209If the last two parameters in your method have the name "req", "res", "request" or "response" apilove will send them to you.
210
211```typescript
212@APIEndpoint({
213 method: "POST",
214 path: "/foo/:what"
215})
216fooX(
217 what: string, // This will be retrieved as a string from the URL
218 @APIParameter({sources: "body"}) data:any, // The body will be parsed and sent back here
219 req?, // Access the raw express.js request
220 res? // Access the raw express.js response
221): Promise<any> {
222
223 return new Promise<any>((resolve, reject) => {
224 resolve(`foo ${what} with some ${data}`);
225 });
226
227}
228```
229
230### configuring your API
231apilove has many configuration options you can modify at any time using environment variables. See the [./lib/APIConfig.ts](./lib/APIConfig.ts) file for more documentation on what you can configure.
232
233### starting your API
234Once you've defined one or more API classes it's time to expose them to the Internets! With apilove it's as simple as this:
235
236```typescript
237import {APILove} from "apilove";
238
239// Assign the output to an export named "handler". You can point your API Gateway/Lambda combo to this handler
240// and apilove will take care of the rest.
241// If apilove detects that it's not running in a Lambda function, it will instead spawn an express.js server.
242module.exports.handler = APILove.start({
243 apis: [
244 {
245 require: "./SampleAPI"
246 }
247 ]
248});
249```
250
251Why do we pass a path to our API code instead of passing an instance? Because apilove will lazy-load your API only when it needs it.
252
253This feature becomes important when you run large APIs on Lambda— because Lambda functions are ephemeral you aren't using precious memory and CPU resources for API endpoints that aren't needed for a given request.
254
255#### APILove.start options
256```typescript
257interface APILoveOptions {
258
259 // One or more APIs to allow apilove to load. Remember these are lazy-loaded.
260 apis?: APILoaderDefinition[];
261
262 // By default cookieParser and bodyParser will be loaded. You can set this to false to prevent those from loading. Defaults to true.
263 loadStandardMiddleware?: boolean;
264
265 // Any other express.js middleware you want loaded before requests make it to apilove.
266 middleware?: [];
267
268 // Override default express.js and APILove error handling
269 defaultErrorHandler?: (error, req, res, next) => void;
270
271 // This can be used to provide a default output for all requests. Useful to return a 404 or other default page.
272 defaultRouteHandler?: (req, res) => void;
273}
274
275interface APILoaderDefinition {
276
277 // The root path to the API endpoint. For example you could specify "/v1" and all endpoints in this API will now be under that root path. Defaults to "/"
278 apiPath?: string;
279
280 // The path to the code for your API. It should work similarly to the standard node.js require function.
281 require: string;
282
283 // By default apilove will look for a class or module with the same name as the source file (i.e. MyAPI.js would look for an exported module called "MyAPI").
284 // You can override this functionality by specifying another name here.
285 moduleName?: string;
286}
287```
288
289### getting started
290The quickest way to get started is:
291
2921. Copy all of the files in the [./example](./example) directory to your root project.
2931. Create your API code.
2941. Modify [APIHandler.ts](./example/APIHandler.ts) to load your APIs.
2951. Run and/or debug APIHandler.js (assuming you've compiled your TypeScript) on your local machine (or even deploy to a server).
296
297**Note: You must enable the emitDecoratorMetadata option in your typescript config (tsconfig.json)**
298
299```json
300{
301 "compilerOptions": {
302 "emitDecoratorMetadata": true
303 }
304}
305```
306
307### deployment
308Deploying apilove APIs with [serverless](https://serverless.com/) is a breeze. We've included a sample server.yml file in [./example/serverless.yml](./example/serverless.yml).
309
310Assuming you've followed the instructions in the previous "getting started" section, simply:
311
3121. Install [serverless](https://serverless.com/) — `npm install -g serverless`.
3131. Modify the [APIDeploymentConfig.json](./example/APIDeploymentConfig.json) file with your particular settings.
3141. Modify the [serverless.yml](./example/serverless.yml) file if you want, but it should mostly work out of the box. (See the file for comments on using the DynamoDB key-value service)
3151. Run `serverless deploy --stage dev` or `sls deploy --stage prod`. You may also need to specify an AWSCLI credential profile with the `--profile myprofile` switch.
316
317That's all there is to it!
\No newline at end of file