Skip to main content

Function Writing Guide

This document introduces how to write function code based on @cloudbase/functions-framework, how to handle errors, function structure, input/output parameters, and provides a series of examples for reference.

Quick Start

Step 1: Write a Function

  1. Create an empty folder locally as the project root directory.
  2. Create a new index.js file as the function entry file.
  3. Fill in the following content in the index.js file:
exports.main = function (event, context) {
const { httpContext } = context;
const { url, httpMethod } = httpContext;
return `[${httpMethod}][${url}] Hello world!`;
};

The above is a simple function code example. The entry function of the function is main, the input parameters of the function are event and context, and the return value of the function is a string.

The current code is a simple function example. The CloudRun Functions Framework supports multiple functions per instance and function routing at the framework level, that is, multiple functions run on the same function instance. For details, refer to: Multiple Functions Per Instance and Function Routing

Step 2: Run the Function

After writing the function code, you can run the function and then call it through HTTP requests.

There are two ways to run the function:

1. Run locally through the command line tool, typically during development and debugging

First, you need to globally install the tcb-ff command line tool:

npm install -g @cloudbase/cli

Execute the following command in the function code root path to run the function locally:

tcb cloudrun run

2. Deploy in the CloudRun environment

You can create a CloudRun Functions Framework service on the CloudBase platform to deploy function code.

To deploy code to the CloudRun Functions Framework, go to CloudBase Platform

Step 3: Call the Function

1. Call a Locally Running Function

Locally, you can use the curl command or other HTTP request tools to call the locally running cloud function.

After the function framework loads and runs the cloud function, it will listen on port http(3000) by default, and you can trigger cloud function execution through http calls.

# Send GET request
curl -v -XGET http://localhost:3000/path/to/xxx

# Send POST request
curl -v -XPOST 'http://127.0.0.1:3000/path/to/xxx \
-H 'Content-Type: application/json' \
--data-raw '{"name":"xxxxx"}'

# Send PUT request
curl -v -XPUT 'http://127.0.0.1:3000/path/to/xxx \
-H 'Content-Type: application/json' \
--data-raw '{"name":"xxxxx"}'

curl -v -XDELETE 'http://127.0.0.1:3000/path/to/xxx

AI bot function request example:

curl -XPOST 'http://127.0.0.1:3000/v1/aibot/bots/ibot-xxxx/send-message' \
-H 'Accept: text/event-stream' \
-H 'Content-Type: application/json' \
--data-raw '{"name":"xxxxx"}'

As shown in the above request examples, @cloudbase/functions-framework supports request methods such as GET, POST, PUT, DELETE, and also supports specifying paths.

2. Call Functions Running in the Cloud Environment

Because functions running in the cloud environment are based on CloudRun, you can call functions by calling CloudRun services.

  1. Call through service domain name. You can query domain name information on the CloudRun Service Page.
  2. Call through HTTP Access Service. After configuring routes on the Access Service Page and associating them with corresponding resources, you can make calls.
  3. Call through Open API, refer to: https://docs.cloudbase.net/http-api/cloudrun/cloudrun-post.
  4. Call through SDK
    1. Server-side @cloudbase/node-sdk calls, refer to:
      1. callFunction: https://docs.cloudbase.net/api-reference/webv2/functions
      2. callContainer: https://docs.cloudbase.net/api-reference/server/node-sdk/cloudrun
    2. Browser @cloudbase/js-sdk calls, refer to:
      1. callFunction: https://docs.cloudbase.net/api-reference/webv2/functions
      2. callContainer: https://docs.cloudbase.net/api-reference/webv2/cloudrun
    3. WeChat Mini Programs can be called through wx.cloud.callContainer, see: Cloud.callContainer
2.1 Example of Calling through callContainer in WeChat Mini Programs
// Container calls must have environment id, cannot be empty
const c1 = new wx.cloud.Cloud({
resourceEnv: "Environment ID",
});
await c1.init();

const r = await c1.callContainer({
path: "/", // Enter custom business path
header: {
"X-WX-SERVICE": "xxx", // Enter service name
},
// Other parameters same as wx.request
method: "POST",
});
console.log(r);

By calling in this way, you can skip configuring custom domain names, certificates and other operations, quickly call functions, and gain security improvements from WeChat's private link.

For more information, refer to wx.cloud.callContainer related documentation:

2.2 Call through Service Domain Name

You can query domain name information on the CloudRun Service Page. The following example is the default domain name:

curl -v -XGET https://{}.run.tcloudbase.com/
2.3 Call through HTTP Access Service

After configuring routes on the Access Service Page and associating them with corresponding resources, you can make calls. The following example is the default domain name:

curl -v -XGET https://{}.app.tcloudbase.com/

Understanding Functions

This section mainly introduces the function's basic structure, input and output, error handling, function logs, and other content.

CloudBase provides TypeScript type definitions for the entry main function @cloudbase/functions-typings@v-1 to assist code writing. Refer to: Function Type Definitions

Function Input Parameters event and context

The basic structure of the function is as follows:

exports.main = function (event, context) {
// Function logic
};

Or

exports.main = async function (event, context) {
// Function logic
};

This file exports a main function, which is the entry method of the function. When a request arrives, this method serves as the starting point for code execution. This method accepts two fixed parameters event and context, representing the input and context of the function, respectively.

Among them, event is the trigger event of the function. In the current HTTP access scenario, it can be considered as the HTTP request body, such as the body of a POST request, the form-data of a multipart/form-data request, the binary file transmitted by a PUT request, etc. If the HTTP request does not pass a request body, event will be an empty object.

context is the context information for function execution and contains the following properties and methods:

NameTypeMeaning
eventIDstringUnique event identifier for context association
eventTypestringEvent type, currently fixed as http
timestampnumberRequest timestamp
httpContexthttpBasisInformation related to the HTTP request
extendedContextRecord<string, unknown>Extended context information provided by the platform, including environment-related information
sse()ISeverSentEventReturns an object for sending Server-Sent Event format responses

httpBasis is defined as follows:

NameTypeMeaning
urlstringComplete URL of this request
httpMethodstringHTTP method of this request, such as GET
headersIncomingHttpHeadersHTTP headers of this request

Among them, the following properties can be obtained from extendedContext:

// import { TcbExtendedContext } from '@cloudbase/functions-typings'
interface TcbExtendedContext {
envId: string; // Environment ID
uin: string; // Request UIN
source: string; // Request source, such as wx
serviceName: string; // Service name
serviceVersion: string; // Service version
authMethod?: string; // Authentication method UNAUTHORIZED | CAM_TC3 | TCB_OAUTH2_B | TCB_OAUTH2_C | WX_SERVER_AUTH
userType?: string; // User type NONE | B_SIDE_USER | C_SIDE_USER
isAdministrator?: boolean; // Whether C-side user (C_SIDE_USER) is an administrator
accessToken?: string; // AccessToken when calling the request
userId?: string; // Request user ID
tmpSecret?: {
// Temporary credentials
secretId: string; // secretId
secretKey: string; // secretKey
token: string; // token
};
wechatContext?: {
// WeChat context information
callId: string; // WeChat call ID
source: string; // Request source, such as wx
appId: string; // AppID of the accessing mini program
openId: string; // OpenID of the accessing user
unionId: string; // UnionID of the accessing user
fromOpenId?: string; // fromOpenID when sharing environment resources
fromUnionId?: string; // fromUnionID when sharing environment resources
fromAppId?: string; // fromAppID when sharing environment resources
};
}

Functions support SSE(Server-Sent Event) format responses. By calling sse(), you can switch to SSE mode and get an ISeverSentEvent instance.

interface ISeverSentEvent {
setEncoder(encoder: TextEncoder): void;
send(event: SseEvent): boolean;
end(msg: SseEvent): void;
}

interface SseEvent<T = string | Record<string, unknown>> {
data: T;
comment?: string;
event?: string;
id?: string;
retry?: number;
}

You can send SSE responses through the send() method, for example:

const { sse } = context;
sse.send({
data: "Hello world!",
});

For more SSE usage examples, refer to: Using Server-Sent Event to Push Messages

When the function execution is completed (that is, returns from the entry function), the return value will be returned directly to the client without additional serialization processing.

Function Output (Return Value)

The final return value of the function execution will be returned to the client as the final return value. At this time, the default HTTP status code (such as 200 for normal return, 204 when there is no response body, 500 when an exception is thrown) and the default HTTP response headers will be returned to the client.

Functions support synchronous and asynchronous modes, and support return values of normal types | Promise<normal types>. You can return return values of Buffer | Stream types to implement file download capabilities.

Note: After the function returns, there may still be asynchronous operations executing in the background.

The return value of the function can support two forms: normal response and integration response.

The function framework distinguishes between the two types of responses by the format of the return value. If the return value format is {statusCode: number, headers: { [key: string]: string | string[] }}, it will be determined as an integration response, and all others will be normal responses.

Normal Response

Normal response returns the return value directly and completely to the client.

Integration Response

Through integration response, you can customize the structure of the HTTP response, including response status code, header fields, etc., and can adopt the return structure of the integration response. The integration response is defined as follows:

interface IntegrationResponse {
statusCode: number;
headers: { [key: string]: string | string[] };
body?: unknown;
}

statusCode is the HTTP response status code, headers are custom HTTP response headers, and body is the HTTP response body. For example, returning the following integration response:

exports.main = function (event, context) {
return {
statusCode: 200,
headers: {
"Content-Type": "text/html",
},
body: {
message: "<h1>Hello world!</h1>",
},
};
};

This will allow HTML to be rendered in the browser.

Error Handling

Exceptions may be thrown during function execution. Exceptions can be divided into the following categories:

  1. Unrecoverable errors: such as failure to load code at startup, Out of Memory (OOM), stack overflow. These errors will cause the function to fail to start or exit from running. Such errors are serious errors that will cause the function instance to restart and should be avoided as much as possible;
  2. Errors during request processing: These errors are usually catchable, and the function needs to handle these errors, record relevant logs, and return relevant information to the client. If user code does not catch relevant exceptions, the function framework will catch the error, record relevant logs, and return error information defined by the framework to the client;
  3. Uncaught exceptions UncaughtException and UnhandledRejection: Usually occur in asynchronous code logic. Because they are uncaught exceptions, the business is not aware of them. These errors will be caught by the framework and relevant information will be printed to the log. Because they usually occur in asynchronous logic, the error will not be reflected in the function return value;
  4. Function framework errors: Some errors may occur inside the function framework, which may be bugs in the function framework code or limitations of the function framework. These errors will be caught by the framework and relevant information will be printed to the log;
  5. Network errors: Exceptions may also occur in the network link between the client and the function instance, causing some errors. Such errors are not errors of the cloud function itself and need to be investigated in combination with the specific deployment platform.

Function Modularization and Dependency Installation

Like normal Node.js code, functions can also organize code through Javascript modularization. For modules that follow the CommonJS specification, you can use require to introduce dependencies. For example:

// index.js
const { add } = require("./sum");

exports.main = function (event, context) {
return add(1, 2);
};

// sum.js
exports.add = function (a, b) {
return a + b;
};

For code using ECMAScript modularization (which needs to be declared in package.json), you can use import statements to introduce dependencies. For example:

// index.mjs
import { add } from './sum.mjs'

export function main(event, context) {
return add(1, 2)
}

// sum.mjs
export function add(a, b) {
return a + b
}

// package.json
{
"name": "example",
"version": "1.0.0",
"main": "index.mjs", // Specify entry file
"type": "module", // Declare modularization method
"dependencies": {
}
}

For external dependency installation, you can declare dependencies in package.json, and the function will automatically install dependency packages according to the dependency declaration during function construction. If there are lock files such as package-lock.json in the directory, they can lock dependency versions.

For cloud functions deployed through CloudRun, the build process supports npm, yarn, and pnpm package dependency management tools and will identify the package management tool used by the code for dependency installation.

Function Logs

Logs printed in function code can be divided into two categories. One is function instance-level logs, that is, logs outside the main entry function. These logs are printed when the function instance starts and modules are loaded, and are not related to any specific request; the other is request-level logs, that is, logs that are printed after a request is triggered and the code execution flow enters the main entry function. These logs are often associated with a specific request and can generally be searched by the request's eventID.

Request-level logs can be further divided into the following categories by content:

  1. Structured access logs (access log) automatically printed for each request, including basic information about the request, such as the hostname handling the request, the accessed URL, request method, response result, request processing time, etc.;
  2. Logs actively printed in function code, that is, logs printed using methods such as console.log in the code. These logs will be associated with the eventID, which is helpful for troubleshooting the specific execution flow of a request;
  3. Exception logs caught by the function framework, such as UncaughtException and UnhandledRejection. These logs will be automatically associated with the eventID and print out exception stack information for troubleshooting.

The logical hierarchy of logs is as follows:

Function logs
├── Function instance logs
├── Request-level logs
│ ├── Access logs
│ ├── Logs printed in function code
│ ├── Exception logs caught by framework

Log content recording location {process.env.CWD}/logs/, specific content is as follows:

  • {process.env.CWD}/logs/accesslog*.log: Access logs, one log for each function call
  • {process.env.CWD}/logs/usercodelog*.log: Logs printed in function code, can be associated with each request through RequestID
  • stdout: Standard output logs, content includes: function instance logs, exception logs caught by the framework, and logs printed by the function framework

For example, the following function code:

// Function instance log
console.log("func initialization started.");

// Function instance log
setTimeout(() => {
console.log("timeout log.");
}, 1000);

exports.main = function (event, context) {
// Request-level log
console.log({ event, context });

setTimeout(() => {
throwReject();
}, 1000);
return "Hello world";
};

async function throwReject() {
// This will trigger an uncaught exception, and because it is not caught, it will be caught by the framework
// The exception is thrown in setTimeout in main, so it will not be reflected in the return value of main
// The exception is caught by the function framework and printed to the log
Promise.reject(new Error("This is an error."));
}

// Function instance log
console.log("func initialization finish.");

When the function instance starts, App started. will be printed. After the request arrives and is processed, the client will receive the Hello world response. 1 second later, the request throws an exception that is caught by the framework. The access log for this request looks like:

{
"@timestamp": "2024-xx-xxTxx:xx:xx.xxxZ", // Timestamp when log was printed
"startAt": "2024-xx-xxTxx:xx:xx.xxxZ", // Time when request processing started
"endAt": "2024-xx-xxTxx:xx:xx.xxxZ", // Time when request processing ended
"logType": "accesslog", // Log type
"hostname": "host", // Hostname handling the request
"pid": 13506, // Process ID
"version": "x.y.z", // Function framework version
"nodeEnv": "", // Node.js related environment variables
"tcbEnv": "", // CloudBase related environment variables
"eventId": "b0900934-79d7-4441-856f-dd46392a5f91", // eventID of this request
"method": "GET", // Request method
"url": "http://127.0.0.1/", // Request URL
"path": "/", // Request path
"status": 200, // Response status code
"httphost": "127.0.0.1", // Hostname of the request
"ua": "PostmanRuntime/7.39.0", // Request User-Agent
"connection": "keep-alive",
"contentType": "application/json", // Request Content-Type
"clientIp": "::1", // Client IP of the request
"resSize": 12, // Response body size
"resContentType": "text/plain", // Response Content-Type
"timeCost": 11, // Total time cost of this request
"userTimeCost": 0 // Time from function main entry start to return from entry
}

Subsequently, the exception will be caught by the framework, which can be associated through eventID(b0900934-79d7-4441-856f-dd46392a5f91), and print out the following stack:

Unhandled Rejection for: Error: This is an error.
at throwReject (/path/to/your/project/index.js:9:18)
at Timeout._onTimeout (/path/to/your/project/index.js:4:22)

Note: Logs are very important for troubleshooting. It is recommended to print logs appropriately in function code for troubleshooting. However, printing a large number of logs will bring other problems, such as logs consuming disk space may cause insufficient disk space, log collection components consuming more CPU for collecting and parsing files, log reporting to storage systems consuming a large amount of network bandwidth, storing a large number of logs leading to increased costs, log writing and query performance degradation and high latency, etc. Therefore, it is recommended to print logs moderately.

Function Routing

The function framework supports splitting a large function into multiple sub-functions at the framework level, and supports routing different requests to different functions by configuring the request path.

Based on the function routing capability, multiple functions can be easily merged into one large function and deployed on the same CloudRun service, which can achieve resource sharing, reduce resource consumption, and save costs.

To use the function routing capability, you need to add a configuration file named cloudbase-functions.json to declare different functions and configure routing.

cloudbase-functions.json file description:

export interface FunctionDefinition {
name: string; // Function name
directory: string; // Function directory, path relative to function functionsRoot
source?: string; // Function entry file, default is index.js
target?: string; // Entry function name, default is main
triggerPath?: string; // Matched path, prefix matching, same effect as path in RouteDefinition, simplifying configuration
}

export interface RouteDefinition {
functionName: string; // Target function name, corresponding to name in FunctionDefinition
path?: string; // Matched path, prefix matching, will call the function corresponding to functionName after matching
}

export interface FunctionsConfig {
functionsRoot: string; // Function root directory, will find and load functions in this directory
functions: FunctionDefinition[]; // Function definition, describing function information, including function name, directory, entry file, etc.
routes: RouteDefinition[]; // Route definition, i.e., path -> functionName
}

Function routing rule description:

  1. Route matching is prefix matching mode. For example, request paths /a, /a/b, /a/b/c can all match the routing rule path=/a
  2. Route matching follows the longest match principle, that is, matches the longest routing rule. For example, /a/b/c will match /a/b first rather than /a
  3. In routing rules, path with or without trailing / has the same routing matching effect. For example, /aa and /aa/ are equivalent, but will not be considered conflicting
  4. Route matching is separated by / into path units, and path units need exact matching. For example, /aaa will not match the /a -> functionA rule
  5. A function can have multiple routes, that is, the same function can be triggered through multiple different path rules. For example, both /a1 and /a2 can be configured to trigger function a
  6. The same route can only point to one function. For example, /a can only point to one function and cannot point to both a and b functions at the same time

Editor support: When developing with VSCode, you can declare the following configuration in .vscode/settings.json to get schema hints for cloudbase-functions.json:

{
"json.schemas": [
{
"fileMatch": ["cloudbase-functions.json"],
"url": "./node_modules/@cloudbase/functions-framework/functions-schema.json"
}
]
}

Example

Assuming the following directory structure:

.
├── cloudbase-functions.json # Multi-function configuration file
├── index.js # Default function entry file
├── Dockerfile # Build file
├── echo # Function echo
│ └── echo.js # Function echo entry file
├── sse # Function sse for handling SSE requests
│ ├── index.mjs
│ └── package.json
├── sum # Function sum
│ ├── index.js
│ ├── package.json
│ └── sum.js
└── ws # Function ws for handling websocket requests
├── client.mjs
├── index.mjs
└── package.json

In the cloudbase-functions.json configuration file, you need to declare and route default function (index.js), echo, sse, sum, ws and other functions. The configuration file is as follows:

{
"functionsRoot": ".", // Function root directory, specified as current directory
"functions": [
// Declare each function and its entry file
{
"name": "default", // Declare default function, i.e., function with entry index.js
"directory": "." // Function directory is current directory
},
{
"name": "echo", // Declare function echo
"directory": "echo", // Declare echo function directory as echo (relative to function root directory)
"source": "echo.js", // Function entry file is echo.js
"triggerPath": "/echo" // Function trigger path, i.e., when request path matches /echo, route to function echo
},
{
"name": "sum", // Declare function sum
"directory": "sum"
},
{
"name": "ws", // Declare function ws
"directory": "ws"
},
{
"name": "sse", // Declare function sse
"directory": "sse"
}
],
"routes": [
// Declare routing rules, can simplify routing rule configuration through triggerPath
{
"functionName": "sum", // Route to function sum, name needs to correspond to the function name defined above
"path": "/sum" // Requests that meet the condition of request path prefix matching /sum match this rule
},
// Requests with path prefix matching /echo route to function echo
{
"functionName": "echo",
"path": "/echo"
},
// Requests with path prefix matching /ws route to function ws
{
"functionName": "ws",
"path": "/ws"
},
// Requests with path prefix matching /sse route to function sse
{
"functionName": "sse",
"path": "/sse"
},
// Requests match to default function by default
{
"functionName": "default",
"path": "/"
}
]
}

Using this configuration file to start the service, requests will be routed to different functions according to different paths:

  • When path prefix matches /sum, route to function sum
  • When path prefix matches /echo, route to function echo
  • When path prefix matches /ws, route to function ws
  • When path prefix matches /sse, route to function sse
  • Requests that cannot match any rule route to function default

Writing Function Code with TypeScript

Compared to JavaScript, writing code through TypeScript can bring many benefits, such as:

  • Better development experience: Editors can provide better code completion, refactoring, and navigation features. This makes the development process more efficient
  • Syntax features: TypeScript supports the latest ECMAScript features, such as decorators, generics, asynchronous functions, etc.
  • Static type checking: Can catch type errors at compile time, reduce runtime errors, and improve code reliability
  • Interfaces and type aliases: TypeScript supports interfaces and type aliases, allowing developers to define complex data structures. This makes code more modular and reusable
  • Readability and maintainability: Through explicit type definitions, TypeScript code is usually easier to understand and maintain
  • Better documentation: Type definitions themselves can serve as documentation, helping developers understand the usage of functions and modules without requiring additional documentation

It is recommended to use TypeScript to write function code, especially in relatively complex projects.

TS Type Definitions

The format of the cloud function entry is to export a main function in the entry file, such as:

// index.ts
export const main = function(event, context) {}
// Equivalent to
export const main = function(event: any, context: any) any {}

In the function declaration of the above example, the function input parameters event, context and the function return value are all of type any. The parameter and return value types are not clear, resulting in no good type-based code completion capability and no type checking constraint capability, which is not conducive to code maintenance and debugging.

For the different characteristics of the above parameters and return values:

  • event is defined by business logic and needs to be type-defined and specified according to business needs
  • context is provided by the cloud function framework and defined by the function framework
  • Function return value is also defined by business logic and needs to be type-defined and specified according to business needs

The cloud function framework provides type definitions for the entry function of cloud functions, which can enhance the perception and constraint capabilities of parameter and return value types.

You can install the @cloudbase/functions-typings package to obtain type definitions for the entry function of cloud functions:

npm install @cloudbase/functions-typings@v-1

After installation, you can introduce the entry function type definition of cloud functions in the function code:

import { TcbEventFunction } from "@cloudbase/functions-typings";

The TcbEventFunction imported in the above example is the entry function type definition of cloud functions, which supports defining the types of function event and context parameters, as well as the type of function return value.

This type is a generic type TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void>

  1. You can define the type of the event parameter through EventT
  2. You can define the type of the function's return value through ResultT

These two types are determined by business logic, and flexible definition methods are open here.

Define the Type of Function event Parameter

You can specify the type of the function event parameter through the first generic parameter EventT of TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void>.

The following is an example of defining the event parameter type:

type jsonEvent = {a: number, b: string, c: boolean, d: {e: string}}
export const main: TcbEventFunction<jsonEvent> = function(event, context) {
event.a
event.b
}

If file upload is performed through FormData, the function framework will place the field corresponding to the file upload in the corresponding field of the event input parameter, with a type of File, which is used to describe file information, such as file name, file size, file path, etc. You can define the event parameter type containing the file File in the following example:

import { TcbEventFunction, File } from "@cloudbase/functions-typings";
type formEvent = { str: string; file: File };
export const main: TcbEventFunction<formEvent> = function (event, context) {
event.str;
event.file.filepath;
};

Define the Type of Function Return Value

You can specify the return value type of the function through the second generic parameter ResultT of TcbEventFunction<EventT = unknown, ResultT extends ReturnT = void>.

Function return values support two types: Normal Response and Integration Response.

For normal responses, just specify the specific type directly. Normal response type example:

import { TcbEventFunction } from "@cloudbase/functions-typings";

// Normal response - with return value, return value type is string
// ReturnT = string
export const main: TcbEventFunction<void, string> = function (event, context) {
return "done.";
};

Integration response can be defined through IntegrationResponse<BodyT = unknown>. The response body type can be defined through BodyT. The following example:

import {
TcbEventFunction,
IntegrationResponse,
} from "@cloudbase/functions-typings";

// Integration response, return value body type is string
// ReturnT = IntegrationResponse<string>
export const main: TcbEventFunction<
void,
IntegrationResponse<string>
> = function (event, context) {
return {
statusCode: 200,
headers: {},
body: "",
};
};

The return value type of the function supports Promise type, such as ReturnT = Promise<string> or ReturnT = Promise<IntegrationResponse<string>>

The following style of function definition can also specify types, but it will be slightly more troublesome. It is recommended to use the style described above.

export function main(event, context) {}

Complete Example

import {
TcbEventFunction,
File,
IntegrationResponse,
} from "@cloudbase/functions-typings";

// GET no body
export const main: TcbEventFunction = function (event, context) {};

// Content-Type: application/json
type jsonEvent = { a: number; b: string };
export const main: TcbEventFunction<jsonEvent> = function (event, context) {
event.a;
event.b;
};

// Content-Type: multipart/form-data
type formEvent = { str: string; file: File };
export const main: TcbEventFunction<formEvent> = function (event, context) {
event.str;
event.file.filepath;
};

// Content-Type: application/octet-stream
export const main: TcbEventFunction<Buffer> = function (event, context) {
event.byteLength;
};

// Access Context information
export const main: TcbEventFunction<void, void> = function (event, context) {
context.extendedContext?.envId;
context.extendedContext?.userId;
};

// Normal response - no return value
export const main: TcbEventFunction = function (event, context) {
return;
};

// Normal response - with return value
export const main: TcbEventFunction<void, string> = function (event, context) {
return "done.";
};

// Integration response
export const main: TcbEventFunction<
void,
IntegrationResponse<string>
> = function (event, context) {
return {
statusCode: 200,
headers: {},
body: "",
};
};

// Async function
export const main: TcbEventFunction<void, Promise<string>> = async function (
event,
context
) {
return new Promise((resolve) => {
setImmediate(() => {
resolve("done.");
});
});
};

// SSE
export const main: TcbEventFunction<void, Promise<string>> = async function (
event,
context
) {
const sse = context.sse?.();

// Optional parameters, set SSE connection request headers
// const sse = context.sse?.({
// keepalive: false, // Whether to keep connection, enabled by default, can be disabled
// headers: {
// 'Mcp-Session-ID': 'this-is-a-mcp-session-id',
// 'X-ABC': ['A', 'B', 'C'],
// }
// })

if (sse && !sse.closed) {
sse.on("close", () => {
console.log("sse closed");
});

sse.send({ data: "hello from sse function, abcedfg..., €€€€€⭐⭐⭐⭐⭐" });

sse.send({
data: "message with linebreak symbol:\\nThis is the first line.\\n\\nThis is the second line.\\r\\r\\r\n",
});

// Send multiple events at once
sse.send([
{ data: "This is the first message." },
{ data: "This is the second message, it\n\r\r\n\r\r\rhas two lines." },
{ data: "This is the third message." },
]);

// The following is an example of sending raw messages
// This method is used to extend the SSE protocol, such as sending other Event Field fields
// Note: There must be a line break at the end for the data to be sent immediately

sse.send("message: This is a raw message. ");
sse.send(["message: This is another raw message.\n\n"]);

const msgs = [
"This is a raw message. \n",
"This is another raw message.\n\n",
];
sse.send(msgs);
}
return "";
};

// WebSocket
export const main: TcbEventFunction = async function (event, context) {
if (context.ws) {
context.ws.on("open", (msg) => {
console.log("open: ", msg);
});
context.ws.on("error", (msg) => {
console.log("error: ", msg);
});
context.ws.on("close", (msg) => {
console.log("close: ", msg);
});
context.ws.on("message", (msg) => {
console.log("message: ", msg);
});
context.ws.send("hello from websocket function");
}
};
main.handleUpgrade = async function (context) {
return {
allowWebSocket: true,
};
};

Project Organization Structure

TypeScript-based Project Structure

.
├── README.md # Project description
├── Dockerfile # Build
├── src # CloudRun function project code directory, each directory corresponds to a function
│ ├── README.md # Cloud function description
│ ├── func-a # Cloud function `func-a` code directory, single function example with multiple functions
│ │ ├── README.md # Current cloud function description
│ │ ├── built # TypeScript compiled code directory
│ │ ├── src # TypeScript source code directory
│ │ ├── package.json
│ │ ├── cloudbase-functions.json # Cloud function declaration and configuration file
│ │ └── tsconfig.json # TypeScript configuration file
│ └── func-b # Cloud function `func-b` code directory, single function example with single function
│ ├── README.md # Current cloud function description
│ ├── built # TypeScript compiled code directory
│ ├── src # TypeScript source code directory
│ ├── package.json
│ └── tsconfig.json # TypeScript configuration file
├── node_modules # node_modules [may exist]
├── package.json # package.json [may exist]
├── tsconfig.json # tsconfig.json [may exist]

The corresponding source code for this project structure can be found at: https://github.com/TencentCloudBase/func-v2-template

The above is an example of a project structure based on TypeScript. The project needs src and built directories to contain source code and compiled code, so you need to configure a tsconfig.json file in the cloud function code. If it is a JavaScript coded project, you do not need directories and files such as src, built, tsconfig.json.

Since each function is deployed independently, each function is also independent of each other, but the code structure is quite similar.

Non-runtime dependent parts can be shared among multiple functions, such as tsconfig.json, common dependencies, etc.

The project may also contain many other types of files, and other files can be organized according to project needs.

The above is a relatively complex project structure for more complex projects. If there is only one cloud function in the entire project directory, you can also organize the directory structure in a single function manner, for example, the following project structure:

.
├── README.md # Current cloud function description
├── built # TypeScript compiled code directory
├── src # TypeScript source code directory
├── Dockerfile # Build file
├── package.json
├── cloudbase-functions.json # Cloud function declaration and configuration file
└── tsconfig.json # TypeScript configuration file

This directory structure moves the files in the project root path/cloudrunfunctions/func-a/* directory to the project root path/ directory. This directory structure is suitable for single function scenarios.