Backend Development 23 min read

Deploying Traditional Web Frameworks to Serverless Platforms with an Adaptation Layer

This article explains how to migrate a traditional Node.js Express web application to a serverless environment by using an adaptation layer that maps function‑compute events to HTTP requests, covering both API‑gateway and HTTP triggers, core implementation steps, and alternative deployment methods.

政采云技术
政采云技术
政采云技术
Deploying Traditional Web Frameworks to Serverless Platforms with an Adaptation Layer

Background

Serverless architectures offer many advantages over traditional deployments, such as no server management, automatic scaling, pay‑per‑use billing, and allowing developers to focus on business logic. However, native serverless frameworks are still limited, and mainstream web frameworks do not directly support serverless deployment. Major Chinese cloud providers (Alibaba Cloud, Tencent Cloud) now provide capabilities to deploy traditional frameworks to serverless quickly and reliably.

We use a Node.js Express application as an example to demonstrate how to deploy it on Alibaba Cloud Function Compute without purchasing or maintaining a virtual machine.

Traditional Application vs. Function Compute Entry Points

Traditional Application Entry File

The typical Express entry file looks like this:

const express = require('express')
const app = express()
const port = 3000

// Handle '/' route
app.get('/', (req, res) => {
  res.send('Hello World!')
})

// Start HTTP server
app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Key points:

The app starts an HTTP server via app.listen() , which internally creates a Node.js http.createServer() instance.

The server listens on the / route and processes requests with a callback function.

Function Compute Entry Functions

In serverless, functions are triggered by events. API‑gateway triggers and HTTP triggers have different entry signatures.

API‑Gateway Trigger Entry

For Node.js, the handler receives event , context , and callback :

/*
 * handler: name must match the handler field when creating the function.
 * event: input data (Buffer), often a JSON string.
 * context: runtime information such as request ID.
 * callback: function to return the result (error, data).
 */
module.exports.handler = (event, context, callback) => {
  // Business logic
  callback(null, data);
};

HTTP Trigger Entry

A simple HTTP function example:

module.exports.handler = function(request, response, context) {
  response.send("hello world");
};

Difference Comparison

Traditional apps start a listening server and handle raw HTTP request/response objects. Serverless functions are event‑driven; the trigger type determines how parameters are mapped. To make a function compute entry compatible with Express, an adaptation layer is required.

Adaptation Layer

The adaptation layer converts the event received by Function Compute into a standard HTTP request, forwards it to the Express app, and then maps the Express response back to the serverless response format.

API‑Gateway Adaptation Layer

Implementation Principle

The layer transforms the API‑gateway event into an HTTP request, lets the traditional web service process it, and finally converts the HTTP response into the API‑gateway response structure.

The core of the adaptation layer is mapping event to Express's request object and mapping Express's response back to the callback data.

Core Process

By analyzing the @webserverless/fc-express source code, we extract three main steps to build a simple adaptation layer.

1. Create a custom HTTP server listening on a Unix Domain Socket

Code (server.js):

// server.js
const http = require('http');
const ApiGatewayProxy = require('./api-gateway-proxy');

function Server(requestListener, serverListenCallback, binaryTypes) {
  this.apiGatewayProxy = new ApiGatewayProxy(this);
  this.server = http.createServer(requestListener);
  this.socketPathSuffix = getRandomString();
  this.binaryTypes = binaryTypes ? binaryTypes.slice() : [];

  this.server.on('listening', () => {
    this.isListening = true;
    if (serverListenCallback) serverListenCallback();
  });

  this.server.on('close', () => { this.isListening = false; })
    .on('error', (error) => { /* error handling */ });
}

Server.prototype.proxy = function(event, context, callback) {
  const e = JSON.parse(event);
  this.apiGatewayProxy.handle({ event: e, context, callback });
};

Server.prototype.startServer = function() {
  return this.server.listen(this.getSocketPath());
};

Server.prototype.getSocketPath = function() {
  if (/^win/.test(process.platform)) {
    const path = require('path');
    return path.join('\\?\pipe', process.cwd(), `server-${this.socketPathSuffix}`);
  } else {
    return `/tmp/server-${this.socketPathSuffix}.sock`;
  }
};

function getRandomString() {
  return Math.random().toString(36).substring(2, 15);
}

module.exports = Server;

This replaces app.listen() with a socket‑based server, allowing the function to forward events as HTTP requests.

2. Convert Function Compute event to an Express request

Code (api-gateway-proxy.js):

// api-gateway-proxy.js
const http = require('http');
const isType = require('type-is');

function ApiGatewayProxy(server) { this.server = server; }

ApiGatewayProxy.prototype.handle = function({ event, context, callback }) {
  this.server.startServer().on('listening', () => {
    this.forwardRequestToNodeServer({ event, context, callback });
  });
};

ApiGatewayProxy.prototype.forwardRequestToNodeServer = function({ event, context, callback }) {
  const resolver = data => callback(null, data);
  try {
    const requestOptions = this.mapContextToHttpRequest({ event, context, callback });
    const req = http.request(requestOptions, response => this.forwardResponse(response, resolver));
    req.on('error', error => { /* error handling */ });
    req.end();
  } catch (error) { /* error handling */ }
};

ApiGatewayProxy.prototype.mapContextToHttpRequest = function({ event, context, callback }) {
  const headers = Object.assign({}, event.headers);
  return {
    method: event.httpMethod,
    path: event.path,
    headers,
    socketPath: this.server.getSocketPath()
  };
};

ApiGatewayProxy.prototype.forwardResponse = function(response, resolver) {
  const buf = [];
  response.on('data', chunk => buf.push(chunk))
          .on('end', () => {
            const bodyBuffer = Buffer.concat(buf);
            const statusCode = response.statusCode;
            const headers = response.headers;
            const contentType = headers['content-type'] ? headers['content-type'].split(';')[0] : '';
            const isBase64Encoded = this.server.binaryTypes && this.server.binaryTypes.length > 0 && !!isType.is(contentType, this.server.binaryTypes);
            const body = bodyBuffer.toString(isBase64Encoded ? 'base64' : 'utf8');
            const successResponse = { statusCode, body, headers, isBase64Encoded };
            resolver(successResponse);
          });
};

module.exports = ApiGatewayProxy;

This module maps the event to an HTTP request, forwards it via a local socket, and converts the HTTP response back to the API‑gateway format.

3. Invoke the Adaptation Layer in the Function Entry

Code (index.js):

// index.js
const express = require('express');
const Server = require('./server.js');

const app = express();
app.all('*', (req, res) => {
  res.send('express-app hello world!');
});

const server = new Server(app);

module.exports.handler = function(event, context, callback) {
  server.proxy(event, context, callback);
};

Deploying the above on Function Compute yields a successful response.

HTTP‑Trigger Adaptation Layer

Implementation Principle

When using an HTTP trigger, the request parameters can be passed directly without conversion. The same socket‑based server forwards the request to Express and returns the response.

Core Process (similar to API‑gateway)

Key steps are identical: create a custom server, map the HTTP trigger context to an HTTP request, forward it, and map the response back.

Relevant code snippets (server.js, api-trigger-proxy.js, index.js) are analogous to the API‑gateway version, with minor differences in how the request body is handled.

Other Serverless Deployment Options

Use an adaptation layer to convert events to HTTP requests.

Custom Runtime – essentially an HTTP server that takes over all request handling.

Custom Container Runtime – package the application and its runtime into a Docker image.

Conclusion

This article presented several ways to migrate traditional web frameworks to serverless platforms, focusing on an adaptation‑layer solution for Express.js on Alibaba Cloud Function Compute. The same principles apply to other frameworks and cloud providers (e.g., Tencent Cloud's tencent-serverless-http ).

References

Webserverless – FC Express extension: https://github.com/awesome-fc/webserverless/tree/master/packages/fc-express

How to migrate web frameworks to Serverless: https://zhuanlan.zhihu.com/p/152391799

Serverless engineering practice – traditional web framework migration: https://developer.aliyun.com/article/790302

Alibaba Cloud – Trigger Introduction: https://help.aliyun.com/document_detail/53102.html

Frontend learning serverless series – WebApplication migration practice: https://zhuanlan.zhihu.com/p/72076708

serverlessCloud ComputingNode.jsAPI GatewayExpressFunction ComputeAdaptation Layer
政采云技术
Written by

政采云技术

ZCY Technology Team (Zero), based in Hangzhou, is a growth-oriented team passionate about technology and craftsmanship. With around 500 members, we are building comprehensive engineering, project management, and talent development systems. We are committed to innovation and creating a cloud service ecosystem for government and enterprise procurement. We look forward to your joining us.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.