diff --git a/starter/node-server/.gitignore b/starter/node-server/.gitignore
new file mode 100644
index 0000000000..fb36784850
--- /dev/null
+++ b/starter/node-server/.gitignore
@@ -0,0 +1,5 @@
+node_modules/
+.vercel
+dist
+*.log
+.DS_Store
diff --git a/starter/node-server/README.md b/starter/node-server/README.md
new file mode 100644
index 0000000000..ff737817f1
--- /dev/null
+++ b/starter/node-server/README.md
@@ -0,0 +1,36 @@
+# Node.js Server Starter
+
+Deploy your Node.js HTTP server to Vercel with zero configuration.
+
+[](https://vercel.com/new/clone?demo-description=Deploy%20a%20Node.js%20HTTP%20server%20with%20zero%20configuration.&demo-title=Node.js%20Server%20Boilerplate&from=templates&project-name=Node.js%20Server%20Boilerplate&repository-name=node-server-boilerplate&repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fexamples%2Ftree%2Fmain%2Fstarter%2Fnode-server&skippable-integrations=1)
+
+Visit the [Node.js on Vercel documentation](https://vercel.com/docs/functions/runtimes/node-js) to learn more.
+
+## Getting Started
+
+Install the required dependencies:
+
+```bash
+pnpm install
+```
+
+## Running Locally
+
+Start the development server on http://localhost:3000
+
+```bash
+pnpm dev
+```
+
+## Deploying to Vercel
+
+Deploy your project to Vercel with the following command:
+
+```bash
+npm install -g vercel
+vercel --prod
+```
+
+Or `git push` to your repository with our [git integration](https://vercel.com/docs/deployments/git).
+
+To view the source code for this template, [visit the example repository](https://github.com/vercel/examples/tree/main/starter/node-server).
diff --git a/starter/node-server/package.json b/starter/node-server/package.json
new file mode 100644
index 0000000000..d09451a71f
--- /dev/null
+++ b/starter/node-server/package.json
@@ -0,0 +1,16 @@
+{
+ "name": "node-server",
+ "repository": "https://github.com/vercel/examples.git",
+ "license": "MIT",
+ "private": true,
+ "type": "module",
+ "scripts": {
+ "dev": "tsx src/server.ts",
+ "type-check": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@types/node": "^22.13.10",
+ "tsx": "^4.19.2",
+ "typescript": "^5.8.3"
+ }
+}
diff --git a/starter/node-server/pnpm-lock.yaml b/starter/node-server/pnpm-lock.yaml
new file mode 100644
index 0000000000..caef05840f
--- /dev/null
+++ b/starter/node-server/pnpm-lock.yaml
@@ -0,0 +1,329 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ devDependencies:
+ '@types/node':
+ specifier: ^22.13.10
+ version: 22.19.21
+ tsx:
+ specifier: ^4.19.2
+ version: 4.22.4
+ typescript:
+ specifier: ^5.8.3
+ version: 5.9.3
+
+packages:
+
+ '@esbuild/aix-ppc64@0.28.0':
+ resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/android-arm64@0.28.0':
+ resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm@0.28.0':
+ resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-x64@0.28.0':
+ resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/darwin-arm64@0.28.0':
+ resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.28.0':
+ resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/freebsd-arm64@0.28.0':
+ resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.28.0':
+ resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/linux-arm64@0.28.0':
+ resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.28.0':
+ resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.28.0':
+ resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.28.0':
+ resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.28.0':
+ resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.28.0':
+ resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.28.0':
+ resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.28.0':
+ resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.28.0':
+ resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/netbsd-arm64@0.28.0':
+ resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.28.0':
+ resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/openbsd-arm64@0.28.0':
+ resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.28.0':
+ resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openharmony-arm64@0.28.0':
+ resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/sunos-x64@0.28.0':
+ resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/win32-arm64@0.28.0':
+ resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.28.0':
+ resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.28.0':
+ resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@types/node@22.19.21':
+ resolution: {integrity: sha512-VMeFBSCKQKmm2swI2kW51SFusDqekC6q9trBCvJ/JliDchFSuoYYKN7yVNjPthP1HKZcx3U1gI/wTcEBjEFKTA==}
+
+ esbuild@0.28.0:
+ resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ fsevents@2.3.3:
+ resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
+ engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
+ os: [darwin]
+
+ tsx@4.22.4:
+ resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==}
+ engines: {node: '>=18.0.0'}
+ hasBin: true
+
+ typescript@5.9.3:
+ resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@6.21.0:
+ resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
+
+snapshots:
+
+ '@esbuild/aix-ppc64@0.28.0':
+ optional: true
+
+ '@esbuild/android-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/android-arm@0.28.0':
+ optional: true
+
+ '@esbuild/android-x64@0.28.0':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/darwin-x64@0.28.0':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.28.0':
+ optional: true
+
+ '@esbuild/linux-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/linux-arm@0.28.0':
+ optional: true
+
+ '@esbuild/linux-ia32@0.28.0':
+ optional: true
+
+ '@esbuild/linux-loong64@0.28.0':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.28.0':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.28.0':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.28.0':
+ optional: true
+
+ '@esbuild/linux-s390x@0.28.0':
+ optional: true
+
+ '@esbuild/linux-x64@0.28.0':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.28.0':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.28.0':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/sunos-x64@0.28.0':
+ optional: true
+
+ '@esbuild/win32-arm64@0.28.0':
+ optional: true
+
+ '@esbuild/win32-ia32@0.28.0':
+ optional: true
+
+ '@esbuild/win32-x64@0.28.0':
+ optional: true
+
+ '@types/node@22.19.21':
+ dependencies:
+ undici-types: 6.21.0
+
+ esbuild@0.28.0:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.28.0
+ '@esbuild/android-arm': 0.28.0
+ '@esbuild/android-arm64': 0.28.0
+ '@esbuild/android-x64': 0.28.0
+ '@esbuild/darwin-arm64': 0.28.0
+ '@esbuild/darwin-x64': 0.28.0
+ '@esbuild/freebsd-arm64': 0.28.0
+ '@esbuild/freebsd-x64': 0.28.0
+ '@esbuild/linux-arm': 0.28.0
+ '@esbuild/linux-arm64': 0.28.0
+ '@esbuild/linux-ia32': 0.28.0
+ '@esbuild/linux-loong64': 0.28.0
+ '@esbuild/linux-mips64el': 0.28.0
+ '@esbuild/linux-ppc64': 0.28.0
+ '@esbuild/linux-riscv64': 0.28.0
+ '@esbuild/linux-s390x': 0.28.0
+ '@esbuild/linux-x64': 0.28.0
+ '@esbuild/netbsd-arm64': 0.28.0
+ '@esbuild/netbsd-x64': 0.28.0
+ '@esbuild/openbsd-arm64': 0.28.0
+ '@esbuild/openbsd-x64': 0.28.0
+ '@esbuild/openharmony-arm64': 0.28.0
+ '@esbuild/sunos-x64': 0.28.0
+ '@esbuild/win32-arm64': 0.28.0
+ '@esbuild/win32-ia32': 0.28.0
+ '@esbuild/win32-x64': 0.28.0
+
+ fsevents@2.3.3:
+ optional: true
+
+ tsx@4.22.4:
+ dependencies:
+ esbuild: 0.28.0
+ optionalDependencies:
+ fsevents: 2.3.3
+
+ typescript@5.9.3: {}
+
+ undici-types@6.21.0: {}
diff --git a/starter/node-server/src/api/routes.ts b/starter/node-server/src/api/routes.ts
new file mode 100644
index 0000000000..3d1ac98fe2
--- /dev/null
+++ b/starter/node-server/src/api/routes.ts
@@ -0,0 +1,41 @@
+import type { IncomingMessage, ServerResponse } from 'node:http'
+
+function sendJson(response: ServerResponse, status: number, data: unknown) {
+ response.writeHead(status, { 'Content-Type': 'application/json' })
+ response.end(JSON.stringify(data))
+}
+
+export function handleApiRequest(
+ request: IncomingMessage,
+ response: ServerResponse,
+ url: URL
+) {
+ if (request.method === 'GET' && url.pathname === '/api/data') {
+ sendJson(response, 200, {
+ data: [
+ { id: 1, name: 'Sample Item 1', value: 100 },
+ { id: 2, name: 'Sample Item 2', value: 200 },
+ { id: 3, name: 'Sample Item 3', value: 300 },
+ ],
+ total: 3,
+ timestamp: '2024-01-01T00:00:00Z',
+ })
+ return
+ }
+
+ const itemMatch = url.pathname.match(/^\/api\/items\/(\d+)$/)
+ if (request.method === 'GET' && itemMatch) {
+ const itemId = Number(itemMatch[1])
+ sendJson(response, 200, {
+ item: {
+ id: itemId,
+ name: `Sample Item ${itemId}`,
+ value: itemId * 100,
+ },
+ timestamp: '2024-01-01T00:00:00Z',
+ })
+ return
+ }
+
+ sendJson(response, 404, { error: 'Not Found' })
+}
diff --git a/starter/node-server/src/pages/home.ts b/starter/node-server/src/pages/home.ts
new file mode 100644
index 0000000000..31b4ce2344
--- /dev/null
+++ b/starter/node-server/src/pages/home.ts
@@ -0,0 +1,85 @@
+export const homePage = `
+
+
+
+
+
+ server.ts
+
+
+
+
+
+
+
+
server.ts
+
+
import { createServer } from 'node:http'
+
+const server = createServer((request, response) => {
+ response.writeHead(200, { 'Content-Type': 'application/json' })
+ response.end(JSON.stringify({ Node: 'on Vercel' }))
+})
+
+server.listen(3000)
+
+
+
+
+
+
Sample Data
+
Access sample JSON data through our REST API. Perfect for testing and development purposes.
+
Get Data →
+
+
+
+
+
+`
diff --git a/starter/node-server/src/server.ts b/starter/node-server/src/server.ts
new file mode 100644
index 0000000000..cee321d08e
--- /dev/null
+++ b/starter/node-server/src/server.ts
@@ -0,0 +1,36 @@
+import { createServer } from 'node:http'
+import { handleApiRequest } from './api/routes.js'
+import { homePage } from './pages/home.js'
+
+const port = Number(process.env.PORT ?? 3000)
+
+const server = createServer((request, response) => {
+ const url = new URL(
+ request.url ?? '/',
+ `http://${request.headers.host ?? 'localhost'}`
+ )
+
+ if (request.method === 'GET' && url.pathname === '/health') {
+ response.writeHead(200, { 'Content-Type': 'application/json' })
+ response.end(JSON.stringify({ status: 'ok' }))
+ return
+ }
+
+ if (request.method === 'GET' && url.pathname === '/') {
+ response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
+ response.end(homePage)
+ return
+ }
+
+ if (url.pathname.startsWith('/api/')) {
+ handleApiRequest(request, response, url)
+ return
+ }
+
+ response.writeHead(404, { 'Content-Type': 'application/json' })
+ response.end(JSON.stringify({ error: 'Not Found' }))
+})
+
+server.listen(port, () => {
+ console.log(`Server listening on http://localhost:${port}`)
+})
diff --git a/starter/node-server/tsconfig.json b/starter/node-server/tsconfig.json
new file mode 100644
index 0000000000..4413ff9dcd
--- /dev/null
+++ b/starter/node-server/tsconfig.json
@@ -0,0 +1,11 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "NodeNext",
+ "moduleResolution": "NodeNext",
+ "strict": true,
+ "skipLibCheck": true,
+ "types": ["node"]
+ },
+ "include": ["src/**/*.ts"]
+}