window.pipedriveLeadboosterConfig={です。 ベース:'leadbooster-chat.pipedrive.com'、 companyId:11580370, playbookUuid: '22236db1-6d50-40c4-b48f-8b11262155be', version: 2、 } ;(function () { var w = window もし (w.LeadBooster) {なら console.warn('LeadBooster already exists') } else { w.LeadBooster = { {. q: [], on: function (n, h) { this.q.push({ t: 'o', n: n, h: h }) }, trigger: 関数 (n) { { this.q.push({ t: 'o', n: n, h: h }) this.q.push({ t: 't', n: n }) }, } } })() Netlify Functionsを使ったGraphQL/MongoDB APIのデプロイ - The Codest
The Codest
  • 会社概要
  • サービス
    • ソフトウェア開発
      • フロントエンド開発
      • バックエンド開発
    • Staff Augmentation
      • フロントエンド開発者
      • バックエンド開発者
      • データエンジニア
      • クラウドエンジニア
      • QAエンジニア
      • その他
    • アドバイザリー
      • 監査&コンサルティング
  • 産業
    • フィンテック&バンキング
    • E-commerce
    • アドテック
    • ヘルステック
    • 製造業
    • 物流
    • 自動車
    • アイオーティー
  • 価値
    • CEO
    • CTO
    • デリバリー・マネージャー
  • チーム
  • Case Studies
  • ノウハウ
    • ブログ
    • ミートアップ
    • ウェビナー
    • リソース
採用情報 連絡先
  • 会社概要
  • サービス
    • ソフトウェア開発
      • フロントエンド開発
      • バックエンド開発
    • Staff Augmentation
      • フロントエンド開発者
      • バックエンド開発者
      • データエンジニア
      • クラウドエンジニア
      • QAエンジニア
      • その他
    • アドバイザリー
      • 監査&コンサルティング
  • 価値
    • CEO
    • CTO
    • デリバリー・マネージャー
  • チーム
  • Case Studies
  • ノウハウ
    • ブログ
    • ミートアップ
    • ウェビナー
    • リソース
採用情報 連絡先
戻る矢印 戻る
2021-05-13
ソフトウェア開発

Netlify Functionsを使ってGraphQL/MongoDB APIをデプロイする

The Codest

パヴェル・リブチンスキ

Software Engineer

ゴール 初期セットアップ 依存関係のインストール それでは始めよう まず、メイン・ディレクトリにtsconfig.jsonを追加する:次に、サーバーの実装用にsrc/server.tsを作成する。次に、ローカル・サーバー用とラムダ用の2つの関数を追加する。リゾルバも型定義もないので、いくつか作る必要がある。最初に、私たちは [...] を望んでいると仮定しよう。

目標

  1. ローカルサーバーとラムダサーバーの両方を設定する。
  2. 両方をMongoDBに接続する。
  3. ベーシック認証を導入する。
  4. サーバーレスApolloのデプロイ GraphQL Netlifyを使ったAPI。
  5. タイプスクリプトの使用.

初期設定

npm init -y

依存関係をインストールする

npm install --save typescript graphql aws-lambda @types/aws-lambda

始めよう

まず tsconfig.json をメインディレクトリに追加する:

 {
 "compilerOptions":{
 "target":"es5"、
 "module": "commonjs"、
 "allowJs": true、
 "strict": true、
 "esModuleInterop": true、
 「skipLibCheck": true、
 「forceConsistentCasingInFileNames": true。
 },
 "include":["src/*.ts", "src/**/*.ts", "src/**/*.js"]、
 "exclude":node_modules"], "exclude": ["node_modules"].
 }

では、次のものを作ってみよう。 src/server.ts サーバーの実装のために。次に2つの関数を追加する。1つはローカル・サーバー用、もう1つはラムダ用だ。

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";


const createLambdaServer = () => 以下のようにします。
  new ApolloServerLambda({
    typeDefs、
    resolvers、
    introspection: true、
    playground: true、
    },
  });

const createLocalServer = () => 以下のようになります。
  new ApolloServer({
    typeDefs、
    resolvers、
    introspection: true、
    playground: true、
    },
  });

export { createLambdaServer, createLocalServer };

さて、リゾルバも型定義もないので、いくつか作る必要がある。まず、ユーザーを作成し、彼らに関する情報を受け取りたいと仮定しよう。

// src/schemas.ts
const { gql } = require("apollo-server-lambda");

const userSchema = gql`
  タイプ User {
    id: ID!
    email:文字列
    name: String!
  }

  型 クエリ
    user(id: ID!):ユーザー!
  }

  型 変異
    createUser(name: String!, email: String!, password: String!):ユーザー!
  }
`;

ご存じない方も多いだろう、 アポロはとても素晴らしいチュートリアルを用意してくれた

それでは、1つのクエリーと1つの突然変異を持つユーザー・リゾルバを作ってみよう。

// src/resolvers.ts
const userResolver = {.
  クエリ:{
    user: async (parent, args, context, info) => { {...
      {...}
    },
  },
  変異:{
    createUser: 非同期 (parent, args, context, info) => { {...
      {...}
    },
  },
};

でもデータがない...修正しよう😉。

型定義とリゾルバをサーバーにインポートすることをお忘れなく。

// src/server.ts
import { ApolloServer as ApolloServerLambda } from "apollo-server-lambda";
import { ApolloServer } from "apollo-server";

import { typeDefs } from "./schemas";
import { resolvers } from "./resolvers";

{...}

mongoose経由でMongoDBと接続する

さて、データベースとの接続を作成しましょう。今回はMongoDBを使う。無料でメンテナンスも簡単だ。その前に、あと2つの依存関係をインストールしましょう:

npm install --save mongoose dotenv

最初のステップはユーザーモデルを作成することです。

// src/model.ts
import mongoose, { Document, Error, Schema } from "mongoose";

エクスポートタイプ User = ドキュメント & {
  _id: string、
  email: string、
  name: string、
  password: string、
};

mongoose.connection.models["User"]を削除します;

const UserSchema:Schema = new Schema({
  email:{
    型:文字列、
    required: true、
    unique: true、
  },
  name: {
    type:文字列、
    required: true、
    minLength: 3、
    maxLength: 32、
  },
  パスワード: {
    type:文字列、
    required: true、
  },
});

export const userModel = mongoose.model  ("User", UserSchema);

パスワードの安全性を高める

セキュリティ第一!それでは、パスワードをハッシュ化することで安全性を確保しよう。

npm install --save bcrypt @types/bcrypt

次に、プレミドルウェアのコールバック内にパスワード・セキュリティを実装する。プリミドルウェアの関数は、各ミドルウェアが次に呼び出すときに次々と実行される。パスワードを安全にするために、ソルトとハッシュを別々の関数呼び出しで生成するテクニックを使っている。

// src/model.ts
bcrypt "からbcryptをインポートする;
{...}
const SALT_WORK_FACTOR: number = 10;

UserSchema.pre("save", function (next) {)
  const user = this as User;
  if (!this.isModified("password")) return next();

  bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) { もし(err)
    if (err) return next(err);

    bcrypt.hash(user.password, salt, function (err, hash) { もし (err)
      if (err) return next(err);
      user.password = hash;
      next();
    });
  });
});
{...}

次に、UserSchemaにcomparePasswordsメソッドを追加する:

// src/model.ts
{...}
UserSchema.methods.comparePasswords = function (
  candidatePassword: string、
  cb: (err: Error | null, same: boolean | null) => void
) {
  const user = this as User;
  bcrypt.compare(candidatePassword, user.password, (err, isMatch) => { もし(err)
    if (err) {
      return cb(err, null);
    }
    cb(null, isMatch);
  });
};
{...}

もちろん、ユーザータイプも変更する。

type comparePasswordFunction = (
  candidatePassword: string、
  cb: (err: Error, isMatch: boolean) => void
) => void;

エクスポートタイプ User = ドキュメント & { { id: string
  _id: string、
  email: string、
  name: string、
  password: string、
  comparePasswords: comparePasswordFunction、
};

これで、サーバーとデータベース間の接続を手配できる。 MONGODB_URI は環境変数で、接続を作成するのに必要な接続文字列を格納します。MongoDB atlas アカウントにログインした後にクラスタパネルから取得できます。これを 環境

// .env
mongodb_uri = ...;

このファイルを .gitignore.素晴らしい!では、データベースとの接続を可能にする関数を追加してみよう。

// src/server.ts
import mongoose, { Connection } from "mongoose";
{...}
let cachedDb:接続;

const connectToDatabase = 非同期 () => { もし(cachedDb)
  if (cachedDb) return;

  await mongoose.connect(process.env.MONGODB_URI || "", {
    useNewUrlParser: true、
    useUnifiedTopology: true、
    useFindAndModify: false、
    useCreateIndex: true、
  });
  cachedDb = mongoose.connection;
};
{...}

コンテキストは、すべてのリゾルバで共有されるオブジェクトです。これを提供するには、ApolloServerコンストラクタにコンテキスト初期化関数を追加するだけです。やってみましょう。

// src/server.ts
import { userModel } from "./models/user.model";
{...}
const createLambdaServer = async () => 以下のようにします。
  new ApolloServerLambda({
    typeDefs、
    resolvers、
    introspection: true、
    playground: true、
    コンテキスト: async () => {
      await connectToDatabase();

      return {
        モデル:{
          userModel、
        },
      };
    },
  });

const createLocalServer = () => 以下のようになります。
  new ApolloServer({
    typeDefs、
    resolvers、
    introspection: true、
    playground: true、
    コンテキスト: async () => {
      await connectToDatabase();

      return {
        モデル:{
          userModel、
        },
      };
    }
  });

おわかりのように、我々はまた ユーザーモデル を通して コンテキスト.これでリゾルバを更新できる:

// resolvers.ts
const userResolver = {.
  クエリ:{
    user: async (_, { email, name, password }, { models: { userModel } }) => {
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  変異:{
    createUser: async (_, { id }, { models: { userModel } }) => {.
      const user = await userModel.create({ email, name, password });
      user を返します;
    },
  },
};

いい感じだ!では、基本的なサーバー・インスタンスを作成してみよう:

// src/index.ts
インポート { createLocalServer } from "./server";
require("dotenv").config();

const port = process.env.PORT || 4000;

const server = createLocalServer();

server.listen(ポート).then(({ url }) => { )
  console.log(`Server ir running at ${url}`);
});

Nodemonによるローカルサーバーの起動

走行前の最後の仕上げに nodemon.json

{
  "watch":["src"]、
  "ext":".ts,.js"、
  "ignore":[],
  "exec":"ts-node --transpile-only ./src/index.ts"
}

スクリプトの追加 package.json

"スクリプト":{
    "start":"nodemon"
  },

そして走る!

npmスタート

そのような情報はターミナル内で得られるはずだ:

サーバーは http://localhost:4000/ で稼動中

はい」の場合は http://localhost:4000/ をブラウザの中に入れてください。

GraphQLプレイグラウンドが表示されるはずです。新しいユーザーを作成しよう!

createUser.png

データベースでの見え方を見てみよう。

データベースJohnDoe.png

かっこいい!すべてうまくいく!

ユーザー情報を入手してみよう。

ユーザー認証なし.png

パスワード・フィールドを追加する

ユーザーパスワード.png

いいねエラー タイプ "User" のフィールド "password" を照会できません。.振り返ってみればわかるように、このフィールドはユーザー・タイプ定義の中に追加したものではありません。意図的にそこにあるのです。パスワードやその他の機密データを照会できるようにすべきではありません。

もうひとつ、認証なしでユーザーデータを取得できる。修正する必要がある。

その前に...

Codegenの設定

GraphQLを使ってみよう コード ジェネレーターを使用して、スキーマに基づいた互換性のあるベース・タイプを取得する。

npm install --save @graphql-codegen/cli @graphql-codegen/introspection
graphql-codegen/typescript @graphql-codegen/typescript-resolvers

作成 codegen.yml

上書き: true
スキーマ"http://localhost:4000"
を生成する:
  ./src/generated/graphql.ts:
    プラグイン
      - "typescript"
      - "typescript-resolvers"(タイプスクリプト-リゾルバ)
  ./graphql.schema.json:
    プラグイン:
      - "introspection"

追加 コドゲン スクリプトを package.json

"スクリプト":{
    "start":"nodemon"、
    "codegen":"graphql-codegen --config ./codegen.yml"、
  },

そして、ローカル・サーバーが起動したら、スクリプトを実行する:

npm run codegen

成功した場合はメッセージが表示されます:

  √ コンフィギュレーションの解析
  √ 出力の生成

その情報を受け取ったら、2つのファイルが表示されるはずだ:

  • Graphql.schema.json メインディレクトリの
  • graphql.ts 新しく作成されたパスに src/生成

我々は2番目のものにもっと興味がある。それを開くと、タイプの素晴らしい構造に気づくだろう。

これでレゾルバを改良できる:

// src/resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";

const userResolver:リゾルバ = {
  クエリ:{
    user: async (_, { id }, { models: { userModel }, auth }):Promise => { { user: async (_ { id })
      const user = await userModel.findById({ _id: id }).exec();
      return user;
    },
  },
  変異:{
    createUser: 非同期 (
      _,
      {メール、名前、パスワード }、
      { models:{userModel }とする。}
    ):Promise => { を実行します。
      コンストラスト user = await userModel.create({
        email、
        名前
        password、
      });
      return user;
    },
  },
};

認証

次に、単純なトークンベースの認証を設定してみよう。

npm install --save jsonwebtoken @types/jsonwebtoken

作成 checkAuth 関数を使用して、トークンが有効かどうかを検証する。結果をコンテキストに追加して、リゾルバ内部でアクセスできるようにします。

// src/server.ts
import { IncomingHttpHeaders } from "http";
import {
  APIGatewayProxyEvent,
  APIGatewayProxyEventHeaders,
  Context,
} from "aws-lambda";
import jwt from "jsonwebtoken";
{...}
const checkAuth = async ({ token } : APIGatewayProxyEventHeaders | IncomingHttpHeaders ) => {
  if (typeof token === "string") {
    try {
      return await jwt.verify(token, "riddlemethis");
    } catch (e) {
      throw new AuthenticationError(`Your session expired. Sign in again.`);
    }
  }
};
{...}
const createLambdaServer = async (
  { headers }: APIGatewayProxyEvent,
  context: Context
) => {
  return new ApolloServerLambda({
    typeDefs,
    resolvers,
    context: async () => {
      await connectToDatabase();

      const auth = await checkAuth(headers);

      return {
        auth,
        models: {
          userModel,
        },
      };
    },
  });
};

function createLocalServer() {
  return new ApolloServer({
    typeDefs,
    resolvers,
    introspection: true,
    playground: true,
    context: async ({ req: { headers } = {} }) => {
      const auth = await checkAuth(headers);
      await connectToDatabase();

      return {
         auth,
         models: {
          userModel,
        },
      };
    },
  });
}

また、そのようなトークンを作成する方法も必要である。最良の方法は、リゾルバ内部にログインクエリを実装することである。

// resolvers.ts
import { Resolvers, Token, User } from "./generated/graphql";

const userResolver:リゾルバ = {
  クエリ:{
    user: async (_, { id }, { models: { userModel }, auth }):Promise => { もし(!auth)
      if (!auth) throw new AuthenticationError("You are not authenticated");

      const user = await userModel.findById({ _id: id }).exec();
      user を返します;
    },
    ログイン: 非同期 (
      _,
      {email, password }、
      { models:{userModel }とする。}
    ):Promise => { を実行します。
      const user = await userModel.findOne({ email }).exec();

      if (!user) throw new AuthenticationError("Invalid credentials");

      const matchPasswords = bcrypt.compareSync(password, user.password);

      if (!matchPasswords) throw new AuthenticationError("Invalid credentials");

      const token = jwt.sign({ id: user.id }, "riddlemethis", { 次のようになります。
        expiresIn: 60、
      });

      return { token };
    },
  },
  変異:{
    createUser: 非同期 (
      _,
      {メール、名前、パスワード }、
      { models:{userModel }とする。}
    ):Promise => { を実行します。
      コンストラスト user = await userModel.create({
        email、
        名前
        password、
      });
      return user;
    },
  },
};

また、トークン・タイプとログイン・クエリによるユーザー・タイプ定義も更新する必要がある。

型トークン
    トークン文字列!
  }

型 クエリー {
    ユーザー(id: ID!):ユーザー!
    login(email: String!, password: String!):トークン!
  }

では、トークンなしでユーザーを取得してみよう。

noAuth.png

OK、うまくいった!ログインしてみる

トークン.png

ヘッダーにトークンを追加して、もう一度ユーザーを取得してみよう。

withToken.png

クールだ!もし間違った認証情報を設定したら?

間違ったEメール.png

いいね!

配備の準備

最後のステップ:Netlifyを使ってサーバーレスapiをデプロイする!

フォルダ作成 ラムダ をメイン・ディレクトリに置き、その中に2つのファイルを置く:

最初に含まれるもの AWS ハンドラです。ApolloServerLambdaサーバーインスタンスを作成し
そして、そのインスタンスのcreateHandlerを使ってハンドラを公開する。

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "????";

export const handler = async (
  イベントAPIGatewayProxyEvent、
  コンテキストコンテキスト
) => {
  const server = await createLambdaServer(event, context);

  return new Promise((res, rej) => { {: (err: Error, args: any) => (err ? rej(err) ?
    const cb = (err: Error, args: any) => (err ? rej(err) : res(args));
    server.createHandler()(event, context, cb);
  });
};

詳しくはこちらをご覧ください。

2つ目はtsconfig。重要なのは アウトディレクトリ フィールドにいる。

// lambda/tsconfig.json
{
  "compilerOptions":{
    "sourceMap": true、
    "noImplicitAny": true、
    "module": "commonjs"、
    "target":"es6"、
    "experimentalDecorators": true、
    「allowSyntheticDefaultImports": true、
    "moduleResolution":"ノード",
    「skipLibCheck": true、
    "esModuleInterop": true、
    "outDir":"./dist"
  },
  "include":["./*.ts", "./**/*.ts", "./**/*.js" ]。
}

しかし、問題がある。私たちは /src なぜならNetlifyはlambdaフォルダの外にはアクセスできないからだ。そのため、バンドルする必要があります。

npm install --save ncp

ディレクトリをコピーするためのパッケージです。

追加 バンドル スクリプトを package.json

"スクリプト":{
    "bundle":"ncp ./src ./lambda/bundle"、
    "codegen":"graphql-codegen --config ./codegen.yml"、
    "start":「nodemon"、
  },

を実行すると npm run bundle新しく作成された ラムダ/バンドル からのすべてのファイルがある。 src/.

インポートパスの更新 lambda/graphql.ts

// lambda/graphql.ts
import { APIGatewayProxyEvent, Context } from "aws-lambda";
import { createLambdaServer } from "./bundle/server";

{...}

ラムダ/バンドルディレクトリを .gitignore

Netlifyでデプロイする

Netlifyにビルドコマンドと関数の場所を教えなければなりません。そのために netlify.toml file:

// netlify.toml
[ビルド]
  コマンド = "npm run build:lambda"
  functions = "lambda/dist"

見ての通り、これは アウトディレクトリ フィールド lambda/tsconfig.json

アプリの構造はこのようになるはずだ(まあ...その一部だが ;)。

アプリ
ラムダ
│ └──バンドル
│ graphql.ts
│ tsconfig.json.ts
└───src
│ └──generated
│ │ graphql.ts
│ index.ts
│ │ model.ts
│ リゾルバ.ts
│ スキーマ.ts
│ サーバー.ts
│
│ codegen.yml
│ graphql.schema.json
│ netlify.toml
│ nodemon.json
│ tsconfig.json

追加 バンドル:ラムダ スクリプトを package.json

"スクリプト":{
    "build:lambda":"npm run bundle && tsc -p lambda/tsconfig.json"、
    "bundle":"ncp ./src ./lambda/bundle"、
    "codegen":"graphql-codegen --config ./codegen.yml"、
    "start":「nodemon"、
  },

デプロイ

Netlifyを使ってアプリをデプロイしてみよう。

  1. Netlifyアカウントにログインしてください、
  2. Githubに接続する、
  3. New site from Git'ボタンをクリックします、
  4. 適切なレポを選択する、
  5. ビルドコマンド npm run build:lambda を設定する、
  6. 環境変数 (MONGODB_URI) を追加、
  7. デプロイ...

そしてTAAADAAAA...

ページが見つかりません.png

これは、エンドポイントがデフォルトでは http://page/ しかし http://page/.netlify/functions/graphql.

リダイレクトなし.png

どうすれば直るのか?とても簡単です。ただ リダイレクト と:

/.netlify/functions/graphql 200!

もう一度デプロイして確認する。

リダイレクト.png

気に入ってもらえただろうか!改良、変更などご自由にどうぞ。

続きを読む

悪いコーディング・プラクティスでプロジェクトを潰さないためには?

ウェブアプリのセキュリティターゲット="_blank" 脆弱性

ウェブアプリのセキュリティ - XSSの脆弱性

関連記事

ソフトウェア開発

将来を見据えたウェブ・アプリケーションの構築:The Codestのエキスパート・チームによる洞察

The Codestが、最先端技術を駆使してスケーラブルでインタラクティブなウェブアプリケーションを作成し、あらゆるプラットフォームでシームレスなユーザー体験を提供することにどのように秀でているかをご覧ください。The Codestの専門知識がどのようにデジタルトランスフォーメーションとビジネス...

ザ・コデスト
ソフトウェア開発

ラトビアを拠点とするソフトウェア開発企業トップ10社

ラトビアのトップソフトウェア開発企業とその革新的なソリューションについて、最新記事でご紹介します。ラトビアの技術リーダーたちがあなたのビジネスをどのように向上させるかをご覧ください。

thecodest
エンタープライズ&スケールアップ・ソリューション

Javaソフトウェア開発の要点:アウトソーシングを成功させるためのガイド

outsourcingのJavaソフトウェア開発を成功させるために不可欠なこのガイドを読んで、The Codestで効率性を高め、専門知識にアクセスし、プロジェクトを成功に導きましょう。

thecodest
ソフトウェア開発

ポーランドにおけるアウトソーシングの究極ガイド

ポーランドのoutsourcingの急増は、経済、教育、技術の進歩がITの成長とビジネス・フレンドリーな環境を促進していることによる。

ザ・コデスト
エンタープライズ&スケールアップ・ソリューション

IT監査ツール&テクニック完全ガイド

IT監査は、安全かつ効率的で、コンプライアンスに準拠したシステムを保証します。その重要性については、記事全文をお読みください。

The Codest
ヤクブ・ヤクボヴィッチ CTO & 共同創設者

ナレッジベースを購読して、IT部門の専門知識を常に最新の状態に保ちましょう。

    会社概要

    The Codest - ポーランドに技術拠点を持つ国際的なソフトウェア開発会社。

    イギリス - 本社

    • オフィス 303B, 182-184 High Street North E6 2JA
      イギリス、ロンドン

    ポーランド - ローカル・テック・ハブ

    • ファブリチュナ・オフィスパーク、アレハ
      ポコジュ18、31-564クラクフ
    • ブレイン・エンバシー, コンストルクトースカ
      11, 02-673 Warsaw, Poland

      The Codest

    • ホーム
    • 会社概要
    • サービス
    • Case Studies
    • ノウハウ
    • 採用情報
    • 辞書

      サービス

    • アドバイザリー
    • ソフトウェア開発
    • バックエンド開発
    • フロントエンド開発
    • Staff Augmentation
    • バックエンド開発者
    • クラウドエンジニア
    • データエンジニア
    • その他
    • QAエンジニア

      リソース

    • 外部ソフトウェア開発パートナーとの協力に関する事実と神話
    • 米国から欧州へ:アメリカの新興企業がヨーロッパへの移転を決断する理由
    • テックオフショア開発ハブの比較:テックオフショア ヨーロッパ(ポーランド)、ASEAN(フィリピン)、ユーラシア(トルコ)
    • CTOとCIOの課題は?
    • The Codest
    • The Codest
    • The Codest
    • Privacy policy
    • ウェブサイト利用規約

    著作権 © 2025 by The Codest。無断複写・転載を禁じます。

    jaJapanese
    en_USEnglish de_DEGerman sv_SESwedish da_DKDanish nb_NONorwegian fiFinnish fr_FRFrench pl_PLPolish arArabic it_ITItalian ko_KRKorean es_ESSpanish nl_NLDutch etEstonian elGreek jaJapanese