Skip to content

Commit 3353eb0

Browse files
committed
feat: Better TypeScript support
Also using Promise instad of callback in main handler
1 parent 3bb59f5 commit 3353eb0

File tree

11 files changed

+244
-205
lines changed

11 files changed

+244
-205
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,26 @@ const getUser = (req, res) => {
6161
exports.handler = use(checkUser, getUser);
6262
```
6363

64-
You can use many popular Express Middlewares. Some examples are:
64+
TypeScript example:
65+
66+
```ts
67+
import { use, Request, Response } from 'lambda-expressless';
68+
import * as bodyParser from "body-parser";
69+
70+
const addUser = (req: Request, res: Response, next: Function) => {
71+
UserService.add(req.body);
72+
73+
// add user
74+
res.json({success: true});
75+
};
76+
77+
export const handler = use(
78+
bodyParser.json(),
79+
addUser
80+
);
81+
```
82+
83+
You can use many popular Express Middlewares. Some tested examples are:
6584

6685
- [body-parser](https://github.com/expressjs/body-parser)
6786

package-lock.json

Lines changed: 20 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
},
2121
"homepage": "https://github.com/muratcorlu/lambda-expressless#readme",
2222
"devDependencies": {
23+
"@types/accepts": "^1.3.5",
24+
"@types/aws-lambda": "^8.10.33",
25+
"@types/connect": "^3.4.33",
2326
"body-parser": "^1.19.0",
2427
"jest": "^24.9.0",
2528
"semantic-release": "^15.13.21"
2629
},
2730
"dependencies": {
28-
"@types/aws-lambda": "^8.10.33",
2931
"accepts": "^1.3.7",
3032
"type-is": "^1.6.18"
3133
}

src/lambda-wrapper.d.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Request } from './request';
22
import { Response } from './response';
33
import { APIGatewayProxyHandler } from 'aws-lambda';
4+
import { NextFunction, NextHandleFunction } from "connect";
45

5-
export interface Middleware {
6-
(request: Request, response: Response, next: Function): void
6+
7+
export interface Middleware extends NextHandleFunction {
8+
(request: Request, response: Response, next: NextFunction): void
79
}
810

9-
export function use(...handlers: Middleware[]): ApiGatewayProxyHandler;
11+
export function use(...handlers: Middleware[]): APIGatewayProxyHandler;
1012

1113
export { Request } from './request';
1214
export { Response } from './response';

src/lambda-wrapper.js

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,33 @@
11
const { Response } = require('./response');
22
const { Request } = require('./request');
33

4+
const promisify = (handler, req, res) => new Promise((resolve, reject) => {
5+
try {
6+
handler(req, res, resolve);
7+
} catch (error) {
8+
reject(error);
9+
}
10+
});
11+
412
exports.use = (...handlers) => {
5-
return (event, context, callback) => {
13+
return (event, context) => {
614
context.callbackWaitsForEmptyEventLoop = false;
715

816
const request = new Request(event);
9-
const response = new Response(request, callback);
17+
const response = new Response(request);
18+
1019
request.res = response;
1120

12-
handlers = [].concat(...handlers);
21+
return new Promise((resolve, reject) => {
22+
23+
response.promise.then(resolve, reject);
1324

14-
handlers.reduce((chain, handler) => chain.then(
15-
() => new Promise((resolve) => {
16-
request.next = resolve;
17-
return handler(request, response, resolve);
18-
})
19-
).catch(callback), Promise.resolve());
25+
[].concat(...handlers).reduce(
26+
(queue, handler) =>
27+
queue.then(() => promisify(handler, request, response)
28+
),
29+
Promise.resolve()
30+
).catch(reject);
31+
});
2032
}
2133
}

src/lambda-wrapper.spec.js

Lines changed: 79 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -2,123 +2,117 @@ const { use } = require('./lambda-wrapper');
22
const bodyParser = require('body-parser');
33

44
describe('Lambda Wrapper', () => {
5-
const proxyRequest = {
6-
body: null,
7-
headers: {},
8-
multiValueHeaders: {},
9-
httpMethod: 'POST',
10-
isBase64Encoded: false,
11-
path: '/path',
12-
pathParameters: { },
13-
queryStringParameters: { },
14-
multiValueQueryStringParameters: { },
15-
stageVariables: { },
16-
requestContext: {},
17-
resource: ''
18-
};
5+
let proxyRequest;
6+
beforeEach(() => {
7+
proxyRequest = {
8+
body: null,
9+
headers: {},
10+
multiValueHeaders: {},
11+
httpMethod: 'POST',
12+
isBase64Encoded: false,
13+
path: '/path',
14+
pathParameters: {},
15+
queryStringParameters: {},
16+
multiValueQueryStringParameters: {},
17+
stageVariables: {},
18+
requestContext: {},
19+
resource: ''
20+
};
21+
});
1922

2023
it('should send json output properly', async () => {
2124

2225
const lambdaHandler = use((req, res) => {
23-
res.json({a: 1});
26+
res.json({ a: 1 });
2427
});
2528

26-
const proxyRequest = {
29+
proxyRequest = {
2730
body: '',
2831
headers: {},
2932
multiValueHeaders: {},
3033
httpMethod: 'GET',
3134
isBase64Encoded: false,
3235
path: '/path',
33-
pathParameters: { },
34-
queryStringParameters: { },
35-
multiValueQueryStringParameters: { },
36-
stageVariables: { },
36+
pathParameters: {},
37+
queryStringParameters: {},
38+
multiValueQueryStringParameters: {},
39+
stageVariables: {},
3740
requestContext: {},
3841
resource: ''
3942
};
40-
const callback = (err, payload) => {
41-
expect(err).toBe(null);
42-
expect(payload).toEqual({
43-
statusCode: 200,
44-
headers: {
45-
'content-type': 'application/json'
46-
},
47-
body: '{"a":1}'
48-
});
49-
};
5043

51-
await lambdaHandler(proxyRequest, {}, callback);
44+
const payload = await lambdaHandler(proxyRequest, {});
45+
expect(payload).toEqual({
46+
statusCode: 200,
47+
headers: {
48+
'content-type': 'application/json'
49+
},
50+
body: '{"a":1}'
51+
});
5252

5353
});
5454

5555

56-
it('should handle json body on a post request', () => {
56+
it('should handle json body on a post request', async () => {
5757

5858
const lambdaHandler = use(bodyParser.json(), (req, res) => {
5959
res.json(req.body);
6060
});
6161

62-
const requestObject = JSON.stringify({a: 1});
62+
const requestObject = JSON.stringify({ a: 1 });
6363

64-
const proxyRequest = {
64+
proxyRequest = {
6565
body: requestObject,
6666
headers: {
6767
'Content-Type': 'application/json',
6868
'Content-Length': requestObject.length
6969
},
7070
multiValueHeaders: {
71-
'Content-Type': [ 'application/json' ],
72-
'Content-Length': [ requestObject.length ]
71+
'Content-Type': ['application/json'],
72+
'Content-Length': [requestObject.length]
7373
},
7474
httpMethod: 'POST',
7575
isBase64Encoded: false,
7676
path: '/path',
77-
pathParameters: { },
78-
queryStringParameters: { },
79-
multiValueQueryStringParameters: { },
80-
stageVariables: { },
77+
pathParameters: {},
78+
queryStringParameters: {},
79+
multiValueQueryStringParameters: {},
80+
stageVariables: {},
8181
requestContext: {},
8282
resource: ''
8383
};
84-
const callback = (err, payload) => {
85-
expect(err).toBe(null);
86-
expect(payload).toEqual({
87-
statusCode: 200,
88-
headers: {
89-
'content-type': 'application/json'
90-
},
91-
body: requestObject
92-
});
93-
}
94-
95-
lambdaHandler(proxyRequest, {}, callback);
9684

85+
const payload = await lambdaHandler(proxyRequest, {});
86+
87+
expect(payload).toEqual({
88+
statusCode: 200,
89+
headers: {
90+
'content-type': 'application/json'
91+
},
92+
body: requestObject
93+
});
9794
});
9895

99-
it('should run multiple middlewares', () => {
96+
it('should run multiple middlewares', async () => {
10097
const lambdaHandler = use((req, res, next) => {
10198
req.params.fromFirstEndpoint = '1';
10299
next();
103100
}, (req, res) => {
104-
res.json({b: req.params.fromFirstEndpoint});
101+
res.json({ b: req.params.fromFirstEndpoint });
105102
});
106103

107-
const callback = (err, payload) => {
108-
expect(err).toBe(null);
109-
expect(payload).toEqual({
110-
statusCode: 200,
111-
headers: {
112-
'content-type': 'application/json'
113-
},
114-
body: JSON.stringify({"b":"1"})
115-
});
116-
}
117-
118-
lambdaHandler(proxyRequest, {}, callback);
104+
const payload = await lambdaHandler(proxyRequest, {});
105+
106+
expect(payload).toEqual({
107+
statusCode: 200,
108+
headers: {
109+
'content-type': 'application/json'
110+
},
111+
body: JSON.stringify({ "b": "1" })
112+
});
119113
});
120114

121-
it('should run multiple middlewares as arrays', () => {
115+
it('should run multiple middlewares as arrays', async () => {
122116
const lambdaHandler = use([(req, res, next) => {
123117
req.params.fromFirstEndpoint = '1';
124118
next();
@@ -132,32 +126,28 @@ describe('Lambda Wrapper', () => {
132126
})
133127
});
134128

135-
const callback = (err, payload) => {
136-
expect(err).toBe(null);
137-
expect(payload).toEqual({
138-
statusCode: 200,
139-
headers: {
140-
'content-type': 'application/json'
141-
},
142-
body: JSON.stringify({
143-
fromFirstEndpoint: '1',
144-
fromSecondEndpoint: '1',
145-
})
146-
});
147-
}
148-
149-
lambdaHandler(proxyRequest, {}, callback);
129+
const payload = await lambdaHandler(proxyRequest, {});
130+
131+
expect(payload).toEqual({
132+
statusCode: 200,
133+
headers: {
134+
'content-type': 'application/json'
135+
},
136+
body: JSON.stringify({
137+
fromFirstEndpoint: '1',
138+
fromSecondEndpoint: '1',
139+
})
140+
});
150141
});
151142

152-
it('should handle errors', () => {
143+
it('should handle errors', async () => {
144+
expect.assertions(1);
145+
153146
const lambdaHandler = use((req, res, next) => {
154-
throw Error('test');
147+
throw new Error('test');
155148
});
156149

157-
const callback = (err, payload) => {
158-
expect(err).toStrictEqual(Error('test'));
159-
}
150+
await expect(lambdaHandler(proxyRequest, {})).rejects.toThrow(Error);
160151

161-
lambdaHandler(proxyRequest, {}, callback);
162-
})
152+
});
163153
});

src/request.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
import { APIGatewayProxyEvent } from 'aws-lambda';
33
import { Accepts } from 'accepts';
44
import { Readable } from 'stream';
5+
import { IncomingMessage } from 'http';
56

6-
export class Request extends Readable {
7+
export interface Request extends IncomingMessage {
78
constructor(event: APIGatewayProxyEvent);
89
headers: { [name: string]: string };
910
hostname: string | null;

src/request.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ class Request extends ReadableStream {
4141
this.host = this.get('X-Forwarded-Host') || this.hostname;
4242
this.xhr = (this.get('X-Requested-With') || '').toLowerCase() === 'xmlhttprequest';
4343

44+
this.url = `${this.headers}${this.path}`;
45+
4446
this.event = event;
4547
this.accept = accepts(this);
4648

0 commit comments

Comments
 (0)