Skip to content

Commit ef1690b

Browse files
committed
feat!: v5.0.0 - Node.js 24 support and removal of deprecated APIs
BREAKING CHANGES: - Minimum Node.js version is now 24 (though earlier versions may work) - Handler no longer accepts callback parameter - async/Promise only - Removed resolutionMode option (CALLBACK/CONTEXT modes removed) - Removed binaryMimeTypes option (use binarySettings instead) - Removed deprecated createServer() and proxy() exports - Removed deprecated handler.handler() and handler.proxy() methods Features: - Full support for AWS Lambda Node.js 24 runtime - Fixed nested routes and custom domains path handling - Configured semantic-release for v5 beta releases
1 parent 422bac1 commit ef1690b

File tree

11 files changed

+101
-280
lines changed

11 files changed

+101
-280
lines changed

.github/workflows/release-on-push-to-mainline.yml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: CICD
22
on:
33
push:
4-
branches: [mainline]
4+
branches: [mainline, v5]
55
pull_request:
66
env:
77
HUSKY: 0
@@ -10,7 +10,7 @@ jobs:
1010
runs-on: ubuntu-latest
1111
strategy:
1212
matrix:
13-
node-version: [18.x, 20.x, 22.x]
13+
node-version: [22.x, 24.x]
1414
steps:
1515
- name: Checkout
1616
uses: actions/checkout@v4
@@ -27,18 +27,18 @@ jobs:
2727
run: npm test
2828

2929
- name: Lint
30-
if: matrix.node-version == '20.x'
30+
if: matrix.node-version == '24.x'
3131
run: npm run lint
3232

3333
- name: Verify Typescript Types
34-
if: matrix.node-version == '20.x'
34+
if: matrix.node-version == '24.x'
3535
run: npm run verify-typescript-types
3636

3737
- name: Audit
38-
if: matrix.node-version == '20.x'
38+
if: matrix.node-version == '24.x'
3939
run: npm audit --audit-level critical
4040
release:
41-
if: github.event_name == 'push' && github.ref == 'refs/heads/mainline'
41+
if: github.event_name == 'push' && (github.ref == 'refs/heads/mainline' || github.ref == 'refs/heads/v5')
4242
needs: test-lint-audit
4343
permissions:
4444
contents: write

UPGRADE.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,60 @@
1+
## From 4.x to 5.x
2+
3+
### Minimum Node.js Version
4+
5+
v5.x officially supports Node.js 24 and later, though it will likely work for earlier versions.
6+
7+
### Handler Changes
8+
9+
The handler no longer accepts a `callback` parameter. It must be used with async/Promise patterns only.
10+
11+
```javascript
12+
// 4.x (still works but callback-based patterns no longer supported)
13+
exports.handler = serverlessExpress({ app })
14+
15+
// 5.x (same, but MUST be async/Promise-based)
16+
export default serverlessExpress({ app })
17+
```
18+
19+
### Removed Options
20+
21+
The following configuration options have been removed:
22+
23+
- `resolutionMode` - Only Promise-based resolution is supported now
24+
- `binaryMimeTypes` - Use `binarySettings` instead
25+
26+
```javascript
27+
// 4.x (deprecated)
28+
serverlessExpress({
29+
app,
30+
resolutionMode: 'CALLBACK', // ❌ Removed
31+
binaryMimeTypes: ['image/*'] // ❌ Removed
32+
})
33+
34+
// 5.x
35+
serverlessExpress({
36+
app,
37+
binarySettings: {
38+
contentTypes: ['image/*']
39+
}
40+
})
41+
```
42+
43+
### Removed Methods
44+
45+
The following deprecated methods have been removed:
46+
47+
- `serverlessExpress.createServer()` - Use `serverlessExpress({ app })` instead
48+
- `serverlessExpress.proxy()` - Use `serverlessExpress({ app })` instead
49+
- `handler.handler()` - Use `handler()` directly
50+
- `handler.proxy()` - Use `handler()` directly
51+
52+
### Proxy Path Fix
53+
54+
v5.x includes a fix for nested routes and custom domains. If you use API Gateway with a custom domain and base path mapping, routes should now work correctly.
55+
56+
---
57+
158
## From 3.x to 4.x
259

360
### Lambda Handler

__tests__/integration.js

Lines changed: 0 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -138,60 +138,6 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
138138
expect(response).toMatchObject(expectedResponse)
139139
})
140140

141-
test('resolutionMode = CALLBACK', (done) => {
142-
const jsonResponse = { data: { name: 'Brett' } }
143-
router.get('/users', (req, res) => {
144-
res.json(jsonResponse)
145-
})
146-
const callback = (e, response) => {
147-
const expectedResponse = makeResponse({
148-
eventSourceName,
149-
body: JSON.stringify(jsonResponse),
150-
multiValueHeaders: {
151-
'content-length': ['25'],
152-
etag: ['W/"19-dkLV0OMoaMM+tzXUD50EB/AHHoI"']
153-
}
154-
})
155-
expect(response).toEqual(expectedResponse)
156-
done()
157-
}
158-
159-
const event = makeEvent({
160-
eventSourceName,
161-
path: '/users',
162-
httpMethod: 'GET'
163-
})
164-
const serverlessExpressInstanceWithCallbackResolutionMode = serverlessExpress({ app, resolutionMode: 'CALLBACK' })
165-
serverlessExpressInstanceWithCallbackResolutionMode(event, {}, callback)
166-
})
167-
168-
test('resolutionMode = CONTEXT', (done) => {
169-
const jsonResponse = { data: { name: 'Brett' } }
170-
router.get('/users', (req, res) => {
171-
res.json(jsonResponse)
172-
})
173-
const context = {}
174-
context.succeed = (response) => {
175-
const expectedResponse = makeResponse({
176-
eventSourceName,
177-
body: JSON.stringify(jsonResponse),
178-
multiValueHeaders: {
179-
'content-length': ['25'],
180-
etag: ['W/"19-dkLV0OMoaMM+tzXUD50EB/AHHoI"']
181-
}
182-
})
183-
expect(response).toEqual(expectedResponse)
184-
done()
185-
}
186-
const event = makeEvent({
187-
eventSourceName,
188-
path: '/users',
189-
httpMethod: 'GET'
190-
})
191-
const serverlessExpressInstanceWithContextResolutionMode = serverlessExpress({ app, resolutionMode: 'CONTEXT' })
192-
serverlessExpressInstanceWithContextResolutionMode(event, context)
193-
})
194-
195141
test('GET missing route', async () => {
196142
const event = makeEvent({
197143
eventSourceName,
@@ -728,93 +674,4 @@ describe.each(EACH_MATRIX)('%s:%s: integration tests', (eventSourceName, framewo
728674
)
729675
})
730676
})
731-
732-
test('legacy/deprecated createServer', async () => {
733-
const serverlessExpressMiddleware = require('../src/middleware')
734-
app = express()
735-
router = express.Router()
736-
app.use('/', router)
737-
router.use(serverlessExpressMiddleware.eventContext())
738-
router.get('/users', (req, res) => {
739-
const { event } = req.apiGateway
740-
const eventPath = event.path || event.rawPath || event.Records[0].cf.request.uri
741-
res.json({
742-
path: eventPath
743-
})
744-
})
745-
const event = makeEvent({
746-
eventSourceName,
747-
path: '/users',
748-
httpMethod: 'GET'
749-
})
750-
const binaryMimeTypes = []
751-
const server = serverlessExpress.createServer(app, null, binaryMimeTypes)
752-
const response = await serverlessExpress.proxy(server, event)
753-
const expectedResponse = makeResponse({
754-
eventSourceName,
755-
body: JSON.stringify({ path: '/users' }),
756-
multiValueHeaders: {
757-
'content-length': ['17'],
758-
etag: ['W/"11-eM8YArY+qNwdvTL2ppeAaFc4Oq8"']
759-
},
760-
statusCode: 200
761-
})
762-
expect(response).toEqual(expectedResponse)
763-
})
764-
765-
test('legacy/deprecated handler', async () => {
766-
const serverlessExpressMiddleware = require('../src/middleware')
767-
router.use(serverlessExpressMiddleware.eventContext())
768-
router.get('/users', (req, res) => {
769-
const { event } = req.apiGateway
770-
const eventPath = event.path || event.rawPath || event.Records[0].cf.request.uri
771-
res.json({
772-
path: eventPath
773-
})
774-
})
775-
const event = makeEvent({
776-
eventSourceName,
777-
path: '/users',
778-
httpMethod: 'GET'
779-
})
780-
const response = await serverlessExpressInstance.handler(event)
781-
const expectedResponse = makeResponse({
782-
eventSourceName,
783-
body: JSON.stringify({ path: '/users' }),
784-
multiValueHeaders: {
785-
'content-length': ['17'],
786-
etag: ['W/"11-eM8YArY+qNwdvTL2ppeAaFc4Oq8"']
787-
},
788-
statusCode: 200
789-
})
790-
expect(response).toEqual(expectedResponse)
791-
})
792-
793-
test('legacy/deprecated proxy', async () => {
794-
const serverlessExpressMiddleware = require('../src/middleware')
795-
router.use(serverlessExpressMiddleware.eventContext())
796-
router.get('/users', (req, res) => {
797-
const { event } = req.apiGateway
798-
const eventPath = event.path || event.rawPath || event.Records[0].cf.request.uri
799-
res.json({
800-
path: eventPath
801-
})
802-
})
803-
const event = makeEvent({
804-
eventSourceName,
805-
path: '/users',
806-
httpMethod: 'GET'
807-
})
808-
const response = await serverlessExpressInstance.proxy({ event })
809-
const expectedResponse = makeResponse({
810-
eventSourceName,
811-
body: JSON.stringify({ path: '/users' }),
812-
multiValueHeaders: {
813-
'content-length': ['17'],
814-
etag: ['W/"11-eM8YArY+qNwdvTL2ppeAaFc4Oq8"']
815-
},
816-
statusCode: 200
817-
})
818-
expect(response).toEqual(expectedResponse)
819-
})
820677
})

__tests__/unit.js

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ describe.skip('forwardResponse: content-type encoding', () => {
289289
}
290290
).then(successResponse => expect(successResponse).toEqual({
291291
statusCode: 200,
292-
body: body,
292+
body,
293293
multiValueHeaders,
294294
isBase64Encoded: false
295295
}))
@@ -345,44 +345,14 @@ describe.skip('forwardResponse: content-type encoding', () => {
345345
})
346346

347347
describe('makeResolver', () => {
348-
test('CONTEXT (specified)', () => {
349-
return new Promise(
350-
(resolve, reject) => {
351-
const context = new MockContext(resolve, reject)
352-
const contextResolver = makeResolver({
353-
context,
354-
resolutionMode: 'CONTEXT'
355-
})
356-
357-
return contextResolver.succeed({
358-
response: 'success'
359-
})
360-
}).then(successResponse => expect(successResponse).toEqual('success'))
361-
})
362-
363-
test('CALLBACK', () => {
364-
const callback = (e, response) => response
365-
const callbackResolver = makeResolver({
366-
callback,
367-
resolutionMode: 'CALLBACK',
368-
context: {}
369-
})
370-
const successResponse = callbackResolver.succeed({
371-
response: 'success'
372-
})
373-
374-
expect(successResponse).toEqual('success')
375-
})
376-
377-
test('PROMISE', () => {
348+
test('succeed resolves promise', () => {
378349
return new Promise((resolve, reject) => {
379350
const promise = {
380351
resolve,
381352
reject
382353
}
383354
const promiseResolver = makeResolver({
384-
promise,
385-
resolutionMode: 'PROMISE'
355+
promise
386356
})
387357

388358
return promiseResolver.succeed({
@@ -392,4 +362,22 @@ describe('makeResolver', () => {
392362
expect(successResponse).toEqual('success')
393363
})
394364
})
365+
366+
test('fail rejects promise', () => {
367+
return new Promise((resolve, reject) => {
368+
const promise = {
369+
resolve: reject,
370+
reject: resolve
371+
}
372+
const promiseResolver = makeResolver({
373+
promise
374+
})
375+
376+
return promiseResolver.fail({
377+
error: new Error('test error')
378+
})
379+
}).then(error => {
380+
expect(error.message).toEqual('test error')
381+
})
382+
})
395383
})

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@codegenie/serverless-express",
3-
"version": "4.17.1",
3+
"version": "5.0.0",
44
"description": "This library enables you to utilize AWS Lambda and Amazon API Gateway to respond to web and API requests using your existing Node.js application framework.",
55
"keywords": [
66
"aws",
@@ -28,7 +28,7 @@
2828
"url": "https://github.com/CodeGenieApp/serverless-express.git"
2929
},
3030
"engines": {
31-
"node": ">=18"
31+
"node": ">=24"
3232
},
3333
"publishConfig": {
3434
"access": "public"
@@ -99,4 +99,4 @@
9999
"path": "./node_modules/@ryansonshine/cz-conventional-changelog"
100100
}
101101
}
102-
}
102+
}

release.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
module.exports = {
22
branches: [
3-
'mainline'
3+
'mainline',
4+
{ name: 'v5', prerelease: 'beta' }
45
],
56
plugins: [
67
[

0 commit comments

Comments
 (0)