メインコンテンツへスキップ
見出し画像

【Rust】ノーコードでAWS Lambda + API構築 + DB操作をやってみた【前編】

    環境構築


    こんにちは。ネオス株式会社の開発技術本部SA部です。
    私たちは、自社プロダクト開発や受託開発を通じて、日々幅広くWebアプリケーションの開発に取り組んでいます。

    この記事では、 実業務で行っているAWSを使ったAPIサーバーの構築をRustというプログラミング言語を使って実装していきたい と思います。
    Rustはここ数年で注目を集めている言語で、「 速い 」「 安全 」「 並行性に強い 」といった特徴を持っており、社内でもRustの活用を推進していきたいと考えています。
    この記事は以下のような方におすすめです。

    • Rustに興味のある方や学び始めている方

    • エンジニア業務への就職を考えている学生の方

    • 生成AIを取り入れた業務効率化を検討しているエンジニアの方

    今回は、 「 フルーツ名と価格の登録・上書き・削除を行う八百屋API 」 についてGPT-4oモデルの生成AIを使用してノーコード開発していきます。

    画像
    今回作成するAPIのイメージ

    それでは環境構築からステップバイステップで進めていきましょう!

    【余談】
    2025年11月14日、AWS LambdaでRustが公式サポートされました!

    環境構築


    本API作成に必要なパッケージをインストールします。
    ※著者はWSL2 Ubuntu 24.04.1 LTS 環境でセットアップしました。

    • Rust

    • AWS CLI (AWSにソースコードをビルドする時に使用)

    • Cargo Lambda (AWS Lambdaプロジェクト作成の為に使用)

    ※1:RustとAWS CLIは上記リンクを参考にインストールしてください。 ※2: Cargo Lambdaのインストールは難ありだったので、こちらでもインストール方法を記載します。

    Rustについてさらに学びたい方は、The Rust Programming Language 日本語版とRust by Example 日本語版も参考になり、おすすめです!

    Cargo Lambda のインストール【AWS Lambda開発に便利!】


    1.まず、以下のコマンドを用いてCargo Lambdaをインストールします。

    curl -fsSL https://cargo-lambda.info/install.sh | sh

    ※今回Cargo Lambdaをインストールした際に、Zig(システムプログラミング向けの言語)がインストールされていない旨のメッセージが表示されました。同様のメッセージが出た場合はこちらからインストールしてください。

    2.実際にAWS Lambdaプロジェクトの作成とテスト用のローカルサーバーの
    起動を試してみましょう。

    cargo lambda new lambda-template-rust && cd lambda-template-rust # プロジェクト初期化コマンド
    cargo lambda watch # ローカルサーバー起動コマンド

    以下内容が出力されれば、OK。

    Hello world, this is an AWS Lambda HTTP request

    プロジェクト作成後、以下のようなディレクトリが作成されます。

    lambda-template-rust/
    ├── src/ 
    │   ├── http_handler.rs # Lambda関数のエントリポイント
    │   └── main.rs # メイン処理
    ├── target/
    ├── .gitignore
    ├── Cargo.lock # 依存関係固定用ファイル
    ├── Cargo.toml # Rustのパッケージマニフェストファイル
    └── README.md

    Cargo Lambdaを使えば、Lambda関数のひな型があらかじめ生成されるため、初期段階からプロジェクトの基本構造が整っています。開発者はこのひな型に必要な処理を追加するだけでよく、実装を効率的に進められるのが大きな利点です。
    それでは、本題のAPIの構築に入っていきましょう。

    八百屋APIの構築


    今回作成するAPIの仕様は以下表の通りです。

    画像
    API仕様

    すぐにAPI処理を書き始めたいところですが、その前に一度、Cargo Lambda が自動生成したLambda関数のひな型ソースコードを見ておきましょう。なお、今回の実装ではsrc/main.rsに処理を追加することはないので簡易的な説明に留めておきます。

    src/main.rs

    use lambda_http::{run, service_fn, tracing, Error}; // 必要な機能のインポート
    mod http_handler; // モジュールの定義
    use http_handler::function_handler; // 関数のインポート
    
    // 非同期メイン関数の定義
    #[tokio::main] 
    async fn main() -> Result<(), Error> {
        tracing::init_default_subscriber(); // トレースの初期化
    
        run(service_fn(function_handler)).await // Lambda関数の実行
    }

    上記src/main.rsがエントリポイントとなり、コア処理を担うsrc/http_handler.rsを呼び出していることが分かります。

    API処理をノーコードで記述していく


    まずはシンプルな処理から実装していきます。src/http_handler.rsに、リクエストのURLパスパラメータから果物の名前を取得し、

    • パラメータが正しく取得できた場合 → 正常レスポンスを返却

    • パラメータが取得できなかった場合 → エラーレスポンスを返却

    という処理を追加してみましょう。 生成AIに以下のように質問しました。

    ▶ Rust Cargo Lambdaを使用してAWS Lambdaのプロジェクトを作成しました。API Gatewayをトリガーにしてイベントリクエストを受け取るのですが、URLのパスパラメータから果物の名前を取得する処理を記述してください。


    AIが生成したソースコードは以下の通りです。

    let fruit_name: Option<&str> = event.path_parameters().first("fruit_name");

    URLパスパラメータから果物の名前を取得する処理が記載できたので、 続けて、以下のように質問しました。

    ▶果物の名前が取得できれば、正常レスポンスとして果物の名前を返却し、取得ができなかったら、エラーレスポンスとしてエラーメッセージを返却するようなRUSTの処理を書いてください。


    AIが生成したコードは以下の通りです。

    if let Some(name) = fruit_name {
        let message: String = format!(r#"{{"ok": "{}"}}"#, name);
        Response::builder()
            .status(200)
            .header("content-type", "application/json")
            .body(Body::from(message))
            .map_err(Error::from)
    } else {
        Response::builder()
            .status(400)
            .header("content-type", "application/json")
            .body(Body::from(r#"{"error": "fruit parameter is required"}"#))
            .map_err(Error::from)
    }

    これで、APIのリクエスト処理とレスポンス部分の記述が終了しました。
    ローカルサーバーを起動して動作確認をしてみましょう。URLのパスパラメータに果物名を指定して、APIにリクエストを送り、その果物名のレスポンスが返ってきたらOKです。

    AIの回答を反映させた最終的なソースコードは以下の通りです。

    src/http_handler.rs

    use lambda_http::{Body, Error, Request, RequestExt, Response}; // 必要な機能のインポート
    
    // レスポンスを生成するための関数
    fn make_response(status: u16, body: String) -> Result<Response<Body>, Error> {
        Response::builder()
            .status(status)
            .header("content-type", "application/json")
            .body(Body::from(body))
            .map_err(Error::from) 
    }
    
    // メイン関数(URLパスパラメータの取得・適切なレスポンス返却)
    pub(crate) async fn function_handler(event: Request) -> Result<Response<Body>, Error> {
        // fruitの存在有無に応じたレスポンスを生成
        if let Some(fruit_name) = event.path_parameters().first("fruit_name") {
            make_response(200, format!(r#"{{"ok": "{}"}}"#, fruit_name))
        } else {
            make_response(400, r#"{"error": "fruit parameter is required"}"#.to_string())
        }
    }

    クレートについて


    クレートとは、Rustにおけるコンパイル可能な最小単位のコードの集まりのことです。 イメージとしては、Pythonにおける「パッケージ」や、Javaにおける「JAR ライブラリ」に相当し、Rustではプロジェクト内の依存関係を Cargo.toml に記述します。 このファイルに、利用したい外部クレートを追加することで、ビルド時に自動的に取得・リンクされます。

    なお、Cargo Lambda プロジェクトを初期化した際には、以下のLambda関数に必要なクレートが自動生成され、初期設定として含まれています。

    Cargo.toml

    [dependencies]
    lambda_http = "0.13.0" // API GatewayやALBなどからのリクエストをHTTPオブジェクト(httpクレート内のstruct)として扱えるようにするクレート
    
    tokio = { version = "1", features = ["macros"] } //非同期ランタイム

    APIの基本機能を一通り準備できたので、 DBのCRUD操作含むAPIの実装を行っていきましょう!

    生成AIにRustを使用したDynamoDB操作 の実装を依頼してみた


    RustでAWSのDynamoDBを操作するLambda関数を作るにあたり、まずは生成AIにコードの骨組み作成をお願いしてみました。Rustは型が厳密で、非同期処理やAWS SDKの扱いも独特なため、慣れていないと戸惑うこともあります。そこで、AIの力を借りて、実装のハードルを下げながら開発を進めてみることにしました。

    まずはAIに依頼してみたこと

    ChatGPTには、以下のような依頼をしました。

    ▶ Rust言語を用いて、AWSのDynamoDBをCRUD操作するLambda関数を作成してください。


    すると、非同期関数でDynamoDBクライアントを初期化し、イベントを受け取って処理するコードを提案してくれました。以下はその一部です。

    client.put_item()
        .table_name("YourTableName")
        .item("PrimaryKey", AttributeValue::S("YourKeyValue".to_string()))
        .item("Attribute", AttributeValue::S("SomeValue".to_string()))
        .send()
        .await?;

    基本的な構文や処理の流れはしっかりしていて、ベースとしては十分使える内容でした。

    AIのコードを動かすための依存クレートの追加

    AIが生成したコードを動かすには、まずCargo.tomlに以下のクレートを追加する必要があります。(AIが提示したコードに合わせて、バージョンも最新のものに調整しています。)

    [dependencies]  aws-config = "1.8.1"  
    aws-sdk-dynamodb = "1.82.0" //AWS DynamoDBを扱えるようにするクレート
    lambda_runtime = "0.14.3"  
    serde = { version = "1", // マクロベースでシリアライズ・デシリアライズを簡略化するクレート
    features = ["derive"] }  
    serde_json = "1.0" // JSONのシリアライズ・デシリアライズを簡略化できるクレート
    tokio = { version = "1", features = ["macros"] 

    ※いくつか修正が必要でした

    生成されたコードをそのまま使おうとすると、例えば以下のようなエラーが出てしまいました。

    error[E0277]: the trait bound String: From<HashMap<String, AttributeValue>> is not satisfied

    これは、DynamoDBのget_item()の戻り値をStringに変換しようとして失敗したものです。Rustでは、戻ってきた HashMap<String, AttributeValue>を適切に取り出して、型変換してあげる必要があります。
    いずれのエラーや不具合も、エラーメッセージや不具合の内容を生成AIに伝えることで、容易に修正できました!

    修正ポイントと工夫したところ


    いずれも生成AIに確認しつつ、容易に実装できました!

    1. Option<T> を使って柔軟なリクエスト処理に

    最初は常に、リクエストボディにfruitとpriceの両方が必須でした。ですが、PUTの場合はURLパスにfruit_nameが含まれているため、ボディにfruitを含めるのは冗長でした。そこで、以下のようにfruitをOption<String>に変更しました。

    #[derive(Serialize, Deserialize)]
    pub struct HttpBody {
        fruit: String,
        price: u32,
    }

    ここで用いたOption<T>という列挙型 (enum) は、RustでNullを扱うのに必須となります。(詳細はRust By Example 日本語版等を参照してください。)

    これにより、POSTではfruitを必須にしつつ、PUTでは省略できるようになり、より自然なAPI設計になりました。

    2. DynamoDBの条件付き更新を導入

    DynamoDBでは、update_item()を使うと指定したキーが存在しない場合でも新しいレコードが作成されてしまいます。それを防ぐために生成AIに確認して、以下のようにcondition_expressionを追加しました。

    .condition_expression("attribute_exists(#name)")
    .expression_attribute_names("#name", "name")
    // ↑「name」はDynamoDBの予約語だったので「#name」として回避した

    生成AIに確認すると、condition_expressionについてCrate aws_sdk_dynamodbに記述があり、DynamoDB condition expression CLI exampleに書かれた CLIの条件を記述できるとのことです。

    これにより、指定したキーが存在しない場合はエラーになり、意図しないレコード作成を防ぐことができました。

    ここまでのコード全文はこちら

    ※ここではエラーハンドリングも追加しています。

    最後に


    今回は、生成AIを活用しながらRust × AWS Lambda上で動作するAPIの処理部分のみを作成しました。

    Rustは、厳格な型チェックによる堅牢さと、自動かつ安全なメモリ管理による安心感、そして高い実行効率を兼ね備えており、AWS Lambdaのように 処理時間とリソースに応じて課金される環境において非常に適した言語 です。
    そのため、「 安全性 」を確保しつつ「 低コスト 」で運用できる点が大きな強みだと感じました。
    一方で、コンパイルエラーの原因解消には手間がかかる場合もあり、そうした場面では生成AIを活用しつつも入念に対処していく必要があると実感しました。 また、AIが示すコードは必ずしも最適解ではないため、「 レビューを通じてプロジェクト要件に合っているかを確認する姿勢が重要 」であると改めて感じました。

    【 後編 】では、このLambda関数を実際にAWSへデプロイ し、動作テストまでの流れを手順に沿ってわかりやすく整理してお伝えします。実装方法の手順や設定のポイントだけでなく、その過程で見えてきた工夫や注意点も交えながら紹介しますので、実際に試してみたい方にとって参考になるはずです。次回もぜひご覧ください!


    あなたへのおすすめ