無伺服器 TypeScript API:使用 Azure Functions 將資料儲存在 MongoDB 中

建立 Azure 函式 API,以使用 Mongoose API 將資料儲存至 Azure Cosmos DB,然後將函式應用程式部署至 Azure 雲端,以使用公用 HTTP 端點裝載。

注意

本文使用目前處於預覽狀態的 Azure Functions Node.js v4 程序設計模型

Flow chart showing path of HTTP request to pass data through Azure Functions and store in Azure Cosmos DB.

準備您的開發環境

安裝下列軟體:

1.在 Visual Studio Code 中登入 Azure

如果您已經使用 Azure 服務延伸模組,則應該已經登入,而且可以略過此步驟。

在 Visual Studio Code 中安裝 Azure 服務延伸模組之後,您必須登入您的 Azure 帳戶。

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  2. 在 [資源]段中,選取 [登入 Azure],然後遵循提示。

    Sign in to Azure through VS Code

  3. 登入之後,請確認 Azure 帳戶的電子郵件地址會出現在狀態列,而您的訂用帳戶出現在 Azure 總管中

    VS Code Azure explorer showing subscriptions

2.建立 Azure 資源群組

資源群組是以區域為基礎的資源集合。 藉由建立資源群組,然後在該群組中建立資源,在教學課程結束時,您可以刪除資源群組,而不需要個別刪除每個資源。

  1. 在您的本機系統上建立新資料夾,以作為 Azure Functions 專案的根目錄。

  2. 在 Visual Studio Code 中開啟此資料夾。

  3. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  4. 在 [資源] 下尋找您的訂用帳戶,+然後選取圖示,然後選取 [建立資源群組]。

  5. 使用下表完成提示:

    提示
    輸入新資源群組的名稱。 azure-tutorial
    選取新資源的位置。 選取靠近您的地理區域。

3.建立本機 Functions 應用程式

建立包含 HTTP 觸發程式函式的本機 Azure Functions(無伺服器)應用程式。

  1. 在 Visual Studio Code 中,開啟命令選擇區 (Ctrl + Shift + P)。

  2. 搜尋並選取 [Azure Functions:建立新專案 ]。

  3. 使用下表完成建立本機 Azure 函式專案:

    提示 備註
    選取將包含函式項目的資料夾 選取目前 (預設) 資料夾。
    選取語言 TypeScript
    選取 TypeScript 程式設計模型 模型 V4 (預覽)
    為您專案的第一個函式選取範本 HTTP 觸發程序 使用 HTTP 要求叫用 API。
    提供函式名稱 blogposts API 路由為 /api/blogposts
  4. 當 Visual Studio Code 建立專案時,請在 檔案中 ./src/functions/blogposts.ts 檢視您的 API 程式代碼。

    import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
    
    export async function blogposts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function processed request for url "${request.url}"`);
    
        const name = request.query.get('name') || await request.text() || 'world';
    
        return { body: `Hello, ${name}!` };
    };
    
    app.http('blogposts', {
        methods: ['GET', 'POST'],
        authLevel: 'anonymous',
        handler: blogposts
    });
    

    此程式代碼是新 v4 程式設計模型中的標準重複使用。 它不是用來指出使用 POST 和 GET 撰寫 API 層的唯一方法。

  5. 將先前的程式代碼取代為下列程式代碼,只允許 GET 要求傳回所有部落格文章。

    import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
    
    // curl --location 'http://localhost:7071/api/blogposts' --verbose
    export async function getBlogPosts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function getBlogPosts processed request for url "${request.url}"`);
    
        // Empty array for now ... will fix later
        const blogposts = [];
    
        return {
            status: 200,
            jsonBody: {
                blogposts
            }
        };
    };
    
    app.get('getBlogPosts', {
        route: "blogposts",
        authLevel: 'anonymous',
        handler: getBlogPosts
    });
    

    您應該注意下列程式代碼的數個 Azure Functions Node.js v4 程式設計模型變更

    • 的函式名稱 getBlobPosts,表示它是 GET 要求,將協助您隔離記錄中的函式。
    • 屬性 route 設定為 blogposts,這是提供 /api/blogposts的預設 API 路由的一部分。
    • 屬性 methods 已移除,而且不必要,因為 app 物件的使用 get 表示這是 GET 要求。 方法函式如下所列。 如果您有不同的方法,則可以使用 methods 屬性傳回 。
      • deleteRequest()
      • get()
      • patch()
      • post()
      • put()

4.啟動 Azurite 本機記憶體模擬器

在您的本機計算機上開發函式需要 儲存體 模擬器(免費)或 Azure 儲存體 帳戶(付費)。

在不同的終端機中 ,啟動 Azurite 本機記憶體模擬器。

azurite --silent --location ./azurite --debug ./azurite/debug.log

這是使用本機 Azure 儲存體 模擬器在本機執行 Azure Functions 的必要專案。 本機記憶體模擬器是使用 AzureWebJobs 儲存體 屬性在 檔案中local.settings.json指定,其值為 UseDevelopmentStorage=true

{
    "IsEncrypted": false,
    "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing"
    }
}

azurite 資料夾已新增至您的 .gitignore 檔案。

5.執行本機無伺服器函式

在本機執行 Azure Functions 專案,以在部署至 Azure 之前進行測試。

  1. 在 Visual Studio Code 中,於 getBlogPosts 函式結尾的 語句上return設定斷點。

  2. 在 Visual Studio Code 中,按 F5 啟動調試程式並附加至 Azure Functions 主機。

    您也可以使用 [偵錯>開始偵錯] 功能表命令。

  3. 輸出會出現在 終端 機面板中。

  4. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  5. 在 [工作區] 區段中,尋找並展開 [本機專案 - Functions ->> getBlogPosts]。

  6. 以滑鼠右鍵按下函式名稱 getBlogPosts,然後選取 [複製函式 URL]。

    Partial screenshot of Visual Studio Code, with the Azure Function's button named Copy Function URL highlighted.

  7. 在您的瀏覽器中,貼上URL,然後選取 Enter,或使用終端機中的下列 cURL 命令:

    curl http://localhost:7071/api/blogposts --verbose
    

    空的部落格文章陣列回應會傳回為:

    *   Trying 127.0.0.1:7071...
    * Connected to localhost (127.0.0.1) port 7071 (#0)
    > GET /api/blogposts HTTP/1.1
    > Host: localhost:7071
    > User-Agent: curl/7.88.1
    > Accept: */*
    >
    < HTTP/1.1 200 OK
    < Content-Type: application/json
    < Date: Mon, 08 May 2023 17:35:24 GMT
    < Server: Kestrel
    < Transfer-Encoding: chunked
    <
    {"blogposts":[]}* Connection #0 to host localhost left intact
    
  8. 在 VS Code 中,停止調試程式 Shift + F5。

6.在 Visual Studio Code 中建立 Azure 函式應用程式

在本節中,您會在 Azure 訂用帳戶中建立函式應用程式雲端資源和相關資源。

  1. 在 Visual Studio Code 中,開啟命令選擇區 (Ctrl + Shift + P)。

  2. 搜尋並選取 Azure Functions:在 Azure 中建立函式應用程式 (進階)

  3. 提示中會提供下列資訊:

    提示 選取項目
    輸入函式應用程式的全域唯一名稱 輸入 URL 路徑中有效的名稱,例如 first-function。 Postpend 3 個字元,讓 URL 成為全域唯一的。 您鍵入的名稱會經過驗證,確定其在 Azure Functions 中是唯一。
    選取運行時間堆疊 選擇 Node.js 18 LTS 或較新版本。
    選取OS 選擇 [Linux]。
    選取新資源的資源群組 建立名為 azure-tutorial-first-function 的新資源群組。 此資源群組最終會有數個資源:Azure 函式、Azure 儲存體 和適用於 MongoDB 的 Cosmos DB API。
    選取主控方案 選擇 [ 取用]。
    選取儲存體帳戶 選取 [建立新的記憶體帳戶 ],並接受預設名稱。
    選取應用程式的 Application Insights 資源。 選取 [建立新的 Application Insights 資源 ],並接受預設名稱。

    等候通知確認應用程式已建立。

7.在 Visual Studio Code 中將 Azure 函式應用程式部署至 Azure

重要

部署到現有的函式應用程式一律會覆寫 Azure 中該應用程式的內容。

  1. 選擇活動列中的 Azure 圖示,然後在 [資源] 區域中,以滑鼠右鍵按兩下您的函式應用程式資源,然後選取 [部署至函式應用程式]。
  2. 如果系統詢問您是否確定要部署,請選取 [部署]。
  3. 部署完成之後,通知會顯示數個選項。 選取 [ 檢視輸出 ] 以檢視結果。 如果您錯過通知,請選取右下角的鈴鐺圖示,再次看到它。

8.將應用程式設定新增至雲端應用程式

  1. 選擇 [活動] 列中的 [Azure] 圖示,然後在 [資源] 區域中展開您的函式應用程式資源,然後以滑鼠右鍵按兩下 [應用程式 設定]。

  2. 選取 [新增設定 ],然後新增下列設定,以啟用 Node.js v4 (預覽) 程序設計模型。

    設定
    AzureWebJobsFeatureFlags EnableWorkerIndexing

9.執行遠端無伺服器函式

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  2. 在 [資源]段中,展開您的 Azure 函式應用程式資源。 以滑鼠右鍵按下函式名稱,然後選取 [ 複製函式 URL]。

  3. 將 URL 貼到瀏覽器中。 當您在本機執行函式時,會傳回相同的空陣列。

    {"blogposts":[]}
    

10.新增適用於 MongoDB API 整合的 Azure Cosmos DB

Azure Cosmos DB 提供 MongoDB API,以提供熟悉的整合點。

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  2. 在 [ 資源] 區段中,選取 + ,然後選取 [ 建立資料庫伺服器]。 使用下表完成建立新 Azure Cosmos DB 資源的提示。

    提示 備註
    選取 Azure 資料庫伺服器 適用於 MongoDB 的 Azure Cosmos DB API
    提供 Azure Cosmos DB 帳戶名稱。 cosmosdb-mongodb-database 加上三個字元以建立唯一的名稱。 名稱會成為 API URL 的一部分。
    定義容量模型。 無伺服器
    選取新資源的資源群組。 azure-tutorial-first-function 選取您在上一節中建立的資源群組。
    選取新資源的位置。 選取建議的區域。

11.安裝 mongoose 相依性

在 Visual Studio Code 終端機中,Ctrl + Shift + `,然後安裝 npm 套件:

npm install mongoose

12.新增部落格文章的mongoose程式碼

  1. 在 Visual Studio Code 中,在 建立./src/名為 lib 的子目錄,建立名為 ./database.ts 的檔案,並將下列程式代碼複製到其中。

    import { Schema, Document, createConnection, ConnectOptions, model, set } from 'mongoose';
    
    const connectionString = process.env.MONGODB_URI;
    console.log('connectionString', connectionString);
    
    const connection = createConnection(connectionString, {
      useNewUrlParser: true,
      useUnifiedTopology: true,
      autoIndex: true
    } as ConnectOptions);
    
    export interface IBlogPost {
      author: string
      title: string
      body: string
    }
    
    export interface IBlogPostDocument extends IBlogPost, Document {
      id: string
      created: Date
    }
    
    const BlogPostSchema = new Schema({
      id: Schema.Types.ObjectId,
      author: String,
      title: String,
      body: String,
      created: {
        type: Date,
        default: Date.now
      }
    });
    
    BlogPostSchema.set('toJSON', {
      transform: function (doc, ret, options) {
          ret.id = ret._id;
          delete ret._id;
          delete ret.__v;
      }
    }); 
    
    export const BlogPost = model<IBlogPostDocument>('BlogPost', BlogPostSchema);
    
    connection.model('BlogPost', BlogPostSchema);
    
    export default connection;
    
  2. 在 Visual Studio Code 中 ./src/functions/blogposts ,開啟 檔案,並以下列內容取代整個檔案的程式代碼:

    import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
    import connection from '../lib/database';
    
    // curl --location 'http://localhost:7071/api/blogposts' --verbose
    export async function getBlogPosts(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function getBlogPosts processed request for url "${request.url}"`);
    
        const blogposts = await connection.model('BlogPost').find({});
    
        return {
            status: 200,
            jsonBody: {
                blogposts
            }
        };
    };
    
    app.get('getBlogPosts', {
        route: "blogposts",
        authLevel: 'anonymous',
        handler: getBlogPosts
    });
    

13.將 連接字串 新增至本機應用程式

  1. 在 Visual Studio Code 的 Azure 總管中,選取 [Azure Cosmos DB ] 區段,然後展開以滑鼠右鍵按下選取您的新資源。

  2. 選取 [複製 連接字串]。

  3. 在 Visual Studio Code 中,使用 [檔案總管] 開啟 ./local.settings.json

  4. 新增名為 MONGODB_URI 的新屬性,並貼上 連接字串的值。

    {
      "IsEncrypted": false,
      "Values": {
        "AzureWebJobsStorage": "",
        "FUNCTIONS_WORKER_RUNTIME": "node",
        "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
        "MONGODB_URI": "mongodb://...."
      }
    }
    

    檔案中的 ./local.settings.json 秘密:

    • 不會部署至 Azure,因為它包含在檔案中 ./.funcignore
    • 不會簽入原始檔控制,因為它包含在檔案中 ./.gitignore
  5. 在本機執行應用程式,並使用上一節中的相同URL測試API。

14.將 連接字串 新增至遠端應用程式

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。
  2. 在 [資源]段中,尋找您的 Azure Cosmos DB 實例。 以滑鼠右鍵按兩下資源,然後選取 [複製 連線 ion 字串]。
  3. 在相同的 [資源] 區段中,尋找您的函式應用程式並展開節點。
  4. 以滑鼠右鍵按兩下 [應用程式] 設定,然後選取 [新增設定]。
  5. 輸入應用程式設定名稱, MONGODB_URI 然後選取 Enter。
  6. 貼上您複製的值,然後按 Enter 鍵。

15.新增用於建立、更新和刪除部落格文章的 API

  1. 在 Visual Studio Code 中,使用命令選擇區來尋找並選取 [Azure Functions:建立函式]。

  2. 選取 [HTTP 觸發程式 ] 並將其 blogpost 命名為 [單一]。

  3. 將下列程式代碼複製到檔案中。

    import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
    import connection, { IBlogPost, IBlogPostDocument }  from '../lib/database';
    
    // curl -X POST --location 'http://localhost:7071/api/blogpost' --header 'Content-Type: application/json' --data '{"author":"john","title":"my first post", "body":"learn serverless node.js"}' --verbose
    export async function addBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function addBlogPost processed request for url "${request.url}"`);
    
        const body = await request.json() as IBlogPost;
    
        const blogPostResult = await connection.model('BlogPost').create({
            author: body?.author,
            title: body?.title,
            body: body?.body
        });
    
        return {
            status: 200,
            jsonBody: {
                blogPostResult
            }
        };
    };
    
    // curl -X PUT --location 'http://localhost:7071/api/blogpost/64568e727f7d11e09eab473c' --header 'Content-Type: application/json' --data '{"author":"john jones","title":"my first serverless post", "body":"Learn serverless Node.js with Azure Functions"}' --verbose
    export async function updateBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function updateBlogPost processed request for url "${request.url}"`);
    
        const body = await request.json() as IBlogPost;
        const id = request.params.id;
    
        const blogPostResult = await connection.model('BlogPost').updateOne({ _id: id }, {
            author: body?.author,
            title: body?.title,
            body: body?.body
        });
    
        if(blogPostResult.matchedCount === 0) {
            return {
                status: 404,
                jsonBody: {
                    message: 'Blog post not found'
                }
            };
        }
    
        return {
            status: 200,
            jsonBody: {
                blogPostResult
            }
        };
    };
    
    // curl --location 'http://localhost:7071/api/blogpost/6456597918547e37d515bda3' --verbose
    export async function getBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function getBlogPosts processed request for url "${request.url}"`);
    
        console.log('request.params.id', request.params.id)
        const id = request.params.id;
    
        const blogPost = await connection.model('BlogPost').findOne({ _id: id });
    
        if(!blogPost) {
            return {
                status: 404,
                jsonBody: {
                    message: 'Blog post not found'
                }
            };
        }
    
        return {
            status: 200,
            jsonBody: {
                blogPost
            }
        };
    };
    
    // curl --location 'http://localhost:7071/api/blogpost/6456597918547e37d515bda3' --request DELETE --header 'Content-Type: application/json' --verbose
    export async function deleteBlogPost(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
        context.log(`Http function deleteBlogPost processed request for url "${request.url}"`);
    
        const id = request.params.id;
    
        const blogPostResult = await connection.model('BlogPost').deleteOne({ _id: id });
    
        if(blogPostResult.deletedCount === 0) {
            return {
                status: 404,
                jsonBody: {
                    message: 'Blog post not found'
                }
            };
        }
    
        return {
            status: 200,
            jsonBody: {
                blogPostResult
            }
        };
    };
    
    app.get('getBlogPost', {
        route: "blogpost/{id}",
        authLevel: 'anonymous',
        handler: getBlogPost
    });
    
    app.post('postBlogPost', {
        route: "blogpost",
        authLevel: 'anonymous',
        handler: addBlogPost
    });
    
    app.put('putBlogPost', {
        route: "blogpost/{id}",
        authLevel: 'anonymous',
        handler: updateBlogPost
    });
    
    app.deleteRequest('deleteBlogPost', {
        route: "blogpost/{id}",
        authLevel: 'anonymous',
        handler: deleteBlogPost
    });
    
  4. 再次使用調試程序啟動本機函式。 下列 API 可供使用:

    deleteBlogPost: [DELETE] http://localhost:7071/api/blogpost/{id}
    getBlogPost: [GET] http://localhost:7071/api/blogpost/{id}
    getBlogPosts: [GET] http://localhost:7071/api/blogposts
    postBlogPost: [POST] http://localhost:7071/api/blogpost
    putBlogPost: [PUT] http://localhost:7071/api/blogpost/{id}
    
  5. 使用 cURL 命令中的 blogpost (單一) API 來新增一些部落格文章。

    curl -X POST --location 'http://localhost:7071/api/blogpost' --header 'Content-Type: application/json' --data '{"author":"john","title":"my first post", "body":"learn serverless node.js"}' --verbose
    
  6. 使用 cURL 命令中的 blogposts (plural) API 來取得部落格文章。

    curl http://localhost:7071/api/blogposts --verbose
    

16.使用適用於 Azure Cosmos DB 的 Visual Studio Code 擴充功能檢視所有數據

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。

  2. 在 [資源] 區段中,以滑鼠右鍵按兩下您的 Azure Cosmos DB 資料庫,然後選取 [重新整理]。

  3. 展開測試資料庫和部落格文章集合節點,以檢視檔。

  4. 選取其中一個專案,以檢視 Azure Cosmos DB 實例中的數據。

    Partial screenshot of Visual Studio Code, showing the Azure explorer with the Databases with a selected item displayed in the reading pane.

17.重新部署函式應用程式以包含資料庫程序代碼

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。
  2. 在 [資源] 區段中,以滑鼠右鍵按下您的 Azure 函式應用程式,然後選取 [部署至函式應用程式]。
  3. 在彈出視窗中,詢問您是否確定要部署,請選取 [ 部署]。
  4. 等到部署完成再繼續。

18.使用雲端式 Azure 函式

  1. 仍在 Azure 檔案總管的 [函式] 區域中,選取並展開您的函式,然後 選取 [函 式] 節點,其中會列出 API
  2. 以滑鼠右鍵按兩下其中一個 API,然後選取 [ 複製函式 URL]。
  3. 編輯先前的 cURL 命令,以使用遠端 URL,而不是本機 URL。 執行命令以測試遠端 API。

19.查詢您的 Azure 函式記錄

若要搜尋記錄,請使用 Azure 入口網站。

  1. 在 Visual Studio Code 中,選取 [Azure 總管],然後在 [函式] 底下,以滑鼠右鍵按兩下您的函式應用程式,然後選取 [在入口網站中開啟]。

    這會開啟 Azure 函式 Azure 入口網站。

  2. 從 設定 選取 [Application Insights],然後選取 [檢視 Application Insights 數據]。

    Browser screenshot showing menu choices. Select **Application Insights** from the Settings, then select **View Application Insights data**.

    此連結會帶您前往使用 Visual Studio Code 建立 Azure 函式時為您建立的個別計量資源。

  3. 從 [監視]段中,選取 [記錄]。 如果出現 [ 查詢 ] 彈出視窗,請選取 快顯右上角的 X 以關閉它。

  4. 在 [新增查詢 1] 窗格中的 [數據表] 索引卷標上,按兩下追蹤數據表。

    這會在查詢視窗中輸入 Kusto 查詢traces

  5. 編輯查詢以搜尋自訂記錄:

    traces 
    | where message startswith "***"
    
  6. 選取執行

    如果記錄檔未顯示任何結果,可能是因為 HTTP 要求與 Azure Function 與 Kusto 中的記錄可用性之間有幾分鐘的延遲。 請稍候幾分鐘,然後再次執行查詢。

    您不需要執行任何額外動作,即可取得此記錄資訊:

    • 程序代碼使用 context.log Function 架構所提供的函式。 藉由使用 context,而不是 console,您可以將記錄篩選為特定的個別函式。 如果您的函式應用程式有許多函式,這會很有用。
    • 函式應用程式已為您新增ApplicationInsights
    • Kusto 查詢工具包含在 Azure 入口網站 中。
    • 您可以選取 traces ,而不必學習撰寫 Kusto 查詢 ,以從您的記錄取得最低資訊。

20.清除資源

因為您使用了單一資源群組,因此您可以藉由刪除資源群組來刪除所有資源。

  1. 在 Visual Studio Code 中,選取主要側邊列中的 Azure 圖示或使用鍵盤快捷方式 (Shift + Alt + A) 來開啟 Azure 總管。
  2. 搜尋並選取 [Azure:依資源群組分組]。
  3. 以滑鼠右鍵按下選取您的資源群組,然後選取 [ 刪除資源群組]。
  4. 輸入資源組名以確認刪除。

可用的原始程式碼

此 Azure 函式應用程式的完整原始碼:

下一步