GraphQL?Amplify Gen2で生成されたGraphQL APIとActiveReportsJSでサクッと帳票

クラウド環境でのシステム開発が当たり前になった今、開発者にとって本当に大切なのは“使いやすさ”かもしれません。
AWSはあらゆる業種・規模のニーズに応えるため、膨大なクラウドサービスを提供していますが、その豊富さゆえ「どこから手を付ければ?」と迷うことも多いはずです。

たとえば、ちょっとしたWebアプリを作りたいときや、認証やデータベースもAWSで揃えたいと考えたとき、専門知識が壁になることもあります。

そんな“クラウドの壁”をぐっと低くしてくれるのが「AWS Amplify Gen2(アンプリファイ ジェネレーションツー)」です。

インフラの細かい設定や複雑なサービス連携はAmplify Gen2が肩代わりし、フロントエンド開発者は「認証(Cognito)」や「GraphQL API(AppSync)」、「ストレージ(S3)」などのバックエンド機能を、コードだけで簡単に組み込めます。

今回は、これまでの記事で紹介してきたAmplify Gen2で作成したWebアプリに、Amplify Gen2で自動生成されるGraphQL API(AppSync)と、「ActiveReportsJS(アクティブレポートJS)」を活用して帳票機能を追加する方法について解説します。

amplifygen2-activereportsjs

GraphQL API(AppSync)とは?

帳票機能の実装に入る前に、まずは本記事で利用するGraphQL API(AppSync)について簡単にご紹介します。

GraphQL API(AppSync)は、AWSが提供するマネージド型のGraphQLサービスです。GraphQLは、クライアントが必要なデータだけを指定して取得できる柔軟なAPIの仕組みで、エンドポイントがひとつで済むのが大きな特徴です。また、独自のクエリ言語を使うことで、複数のテーブルやリソースから必要なデータを一度にまとめて取得することができます。これにより、従来のREST APIのように複数のエンドポイントへ何度もアクセスする必要がなく、通信量や開発工数を大幅に削減できます。

Amplify Gen2では、GraphQLのスキーマを定義するだけで、その内容に基づいたバックエンド(APIやデータベース、認証など)が自動生成されるため、インフラの専門知識がなくても効率的かつセキュアなAPIをサーバーレスで簡単に構築できます。

ActiveReportsJSでもGraphQLに対応しており、レポート作成時にGraphQLエンドポイントへクエリをPOSTすることで、取得したデータをそのままデータセットとして活用することが可能です。これにより、柔軟なデータ取得と帳票作成をシームレスにつなげることができます。

事前準備

今回は、以下の記事で作成したWebアプリケーションに、ActiveReportsJSを利用して帳票機能を追加していきます。
記事内に記載されている、Webアプリケーションの実装を事前に行ってください。

ActiveReportsJSで帳票機能を実装

それでは、事前に準備したWebアプリケーションに対して、ActiveReportsJSを活用し、帳票機能を追加していきます。

ActiveReportsJSのインストール

まず、作成済みのWebアプリケーション「amplify-gen2-app」のプロジェクトをVSCodeで開き、ターミナルから以下のコマンドを実行してActiveReportsJSのReact対応パッケージとビューワを日本語化するためのローカライズパッケージをインストールします。
※ React対応パッケージは@mescius/activereportsjsパッケージに依存しており、React対応パッケージをインストールすると同時にインストールされます。

npm install @mescius/activereportsjs-react@5.1.4  @mescius/activereportsjs-i18n@5.1.4

コンポーネントの実装

つづいて、コンポーネントを実装していきます。以下のように「components」フォルダ内に「ReportViewer.tsx」を追加します。

コンポーネント実装

ActiveReportsJSはクライアントサイドでのみ動作するため、サーバーサイドレンダリングに対応した「Next.js」を使用する場合は、該当コンポーネントがクライアントサイドで実行されることを'use client';の指定によって明示する必要があります。「ReportViewer.tsx」に実装するコードは以下の通りです。

'use client';

import { Core } from "@mescius/activereportsjs";  // ActiveReportsJSのCoreライブラリを読み込み、ライセンス設定などに使用
import { Viewer,Props as ViewerProps} from "@mescius/activereportsjs-react"; // ActiveReportsJSViewerコンポーネント,Props型のインポート
import "@mescius/activereportsjs/pdfexport";      // PDFエクスポートモジュール
import "@mescius/activereportsjs-i18n";           // ローカライズモジュール

import useLicense from '../hooks/useLicense';
import React, { useEffect, useRef } from "react"; 

// ActiveReportsJSのCSS設定
import "@mescius/activereportsjs/styles/ar-js-ui.css";
import "@mescius/activereportsjs/styles/ar-js-viewer.css";


// ViewerWrapper コンポーネント
const ViewerWrapper = (props: ViewerWrapperProps) => {
  // ライセンス読み込み状態
  const isLicenseLoaded = useLicense(Core, 'activereportsjs');
  // Viewerコンポーネントにアクセスするためのrefを作成
  const viewerRef = useRef<Viewer>(null);

  useEffect(() => {
    if (isLicenseLoaded && viewerRef.current) {
      viewerRef.current.Viewer.open(props.reportUri);
    }
  }, [props.reportUri, isLicenseLoaded]);

  // ライセンスが読み込まれるまでは、ローディングメッセージを表示
  if (!isLicenseLoaded) {
    return <div>Loading license...</div>;
  }

  // Viewerレンダリング
  return <Viewer {...props} ref={viewerRef} />;
};

// ViewerWrapperProps型
export type ViewerWrapperProps = ViewerProps & { reportUri: string };

// ViewerWrapperコンポーネントをデフォルトエクスポート
export default ViewerWrapper;

ページ実装

つづいて、コンポーネントを呼び出すためのページを作成するために「app」フォルダ配下に「activereportsjsviewer」フォルダを作成し、その中に「page.tsx」を追加します。

activereportsjsviewerページの追加

「page.tsx」ファイルは以下のように実装します。「components/ReportViewer.tsx」ファイルを動的インポートし、その際{ ssr: false }オプションを指定することで対象ファイルをSSR(サーバーサイドレンダリング)しないように設定します。

"use client";
import type { NextPage } from "next";
import React from "react";
import { ViewerWrapperProps } from "../../components/ReportViewer";

// 動的インポートを使用して、レポートビューワのラッパーをロードします。詳細については、「https://nextjs.org/docs/advanced-features/dynamic-import」を参照してください。
import dynamic from "next/dynamic";
const Viewer = dynamic<ViewerWrapperProps>(
  async () => {
    return (await import("../../components/ReportViewer")).default;
  },
  { ssr: false }
);

const Home: NextPage = () => {
  return (
    <div
      style={{ width: "100%", height: "100vh" }}
    >
      <Viewer reportUri="reports/Invoice_green.rdlx-json" language="ja" />
    </div>
  );
};

export default Home;

レポートファイルの作成

つづいて、帳票として出力するレポートファイルを作成します。今回はGitHubに公開中の以下のレポートを使用します。

GitHubより取得したレポートファイルは、次のように「public」フォルダに「reports」フォルダを作成し、その中に格納します。

activereportsjsviewerページの追加

データソースを変更する

格納したファイルをActiveReportsJSデザイナより開き、データソースの設定を変更します。次のようにデータソースの編集アイコンを選択してデータソースダイアログを開きます。ダイアログ表示後、「形式」を「外部ファイルまたはURL」へ変更します。

データソースの設定変更1

変更後はエンドポイントの指定が可能となりますので、ここにAmplify Gen2で自動生成されるバックエンドのGraphQL API(AppSync)を設定します。また、データセットを作成するために、一時的にヘッダに以下のように「x-api-key」を指定します。
※ 「x-api-key」の指定はセキュリティ上推奨される方法ではなく、データセット作成のための一時的な措置です。本番環境では、後ほど解説するCognito認証を用いて取得したトークン(ベアラートークン)による認証方法に変更してください。

データソースの設定変更2

GraphQL API(AppSync)のエンドポイントとAPI Keyは、次のように「amplify_outputs.json」に記載されています。

GraphQL APIのエンドポイント

「amplify_outputs.json」がない場合は、次のようにデプロイ済みAmplify環境よりダウンロードしてください。また、ローカル環境で実行する場合は、サンドボックス実行時に配置済みの「amplify_outputs.json」が上書きされ、サンドボックス環境のエンドポイントに情報が書き換わります。本番環境へデプロイする際は、本番環境のエンドポイントへ変更してください。

amplify_outputs.jsonのダウンロード

データセットを変更する

つづいて、データセットの設定を変更します。データセットの編集アイコンを選択しデータセットダイアログを開きます。ダイアログ表示後、「メソッド」を「HTTP POST」に変更。POST内容には、GraphQLクエリ「{{"query":"query MyQuery {{listInvoices {{items {{InvoiceID BillNo SlipNo CustomerID CustomerName Products UnitPrice Number Date createdAt updatedAt}}}","variables":{{}}」を設定します。

さらに「ヘッダ」に「Content-Type」と値に「application/json」を設定、「JSONパス」に「$.data.listInvoices.items.*」を設定し[検証]ボタンを押します。

データセットの設定変更1

[検証]ボタンを押すと次のようにデータベースフィールドが自動で設定されます。設定されたフィールド「UnitPrice」と「Number」を利用し、計算フィールドに「Price」フィールドを追加し、「値」に「{UnitPrice * Number}」を設定し、[変更を保存]ボタンを押します。

データセットの設定変更2

データソースとデータセットの設定を変更し、プレビューを実行すると、次の動画のように、GraphQL APIからデータが取得され、登録済みのデータが帳票としてプレビュー表示されます。

GraphQL取得の認証方法を変更する

ここまでで、レポートファイルのデータソースおよびデータセットを変更し、レポートデザイナー上でプレビューによる動作確認を行うことができました。

本番環境での利用に向けて、現在のAPI Key認証から、Cognito認証を用いて取得したトークン(ベアラートークン)による認証方法へ変更します。

レポートファイルにレポートパラメータを追加する

まず、最初にWebアプリケーションで取得した、トークン情報をレポートに引き渡すためのレポートパラメータを以下のように追加します。

レポートパラメータの追加

レポートパラメータの名称は「Bearer」として作成します。

レポートパラメータの追加2

レポートファイルのデータソースヘッダを変更する

先ほどレポートのデータソース設定でヘッダーに追加した「x-api-key」の設定を、以下のように「Authorization」に変更し、追加したレポートパラメータ「Bearer」の値を渡すようにします。

ヘッダ情報の変更

ログインUIの実装

つづいて、Webアプリケーションに、Cognito認証のログインUIの実装を行います。こちらについては、AWS Amplifyの公式ドキュメントを参考に実装してください。

実装したコードは以下の通りです。

app/AuthenticatorWrapper.tsx
app/layout.tsx
"use client";	 
import type { Metadata } from "next";	 
import { Inter } from "next/font/google";	 
import { Amplify } from "aws-amplify";	 
import "./app.css";	 
import AuthenticatorWrapper from "./AuthenticatorWrapper";	 
import "@aws-amplify/ui-react/styles.css";	 
import outputs from "@/amplify_outputs.json";	 
const inter = Inter({ subsets: ["latin"] });	 
Amplify.configure(outputs);	 
export default function RootLayout({	 
 children,	 
}: {	 
 children: React.ReactNode;	 
}) {	 
 return (	 
 <html lang="en">	 
 <body> 	 
 <AuthenticatorWrapper>	 
 {children}	 
 </AuthenticatorWrapper>	 
 </body>	 
 </html>	 
 );	 
}
app/page.tsx
"use client";	 
import { useState, useEffect } from "react";	 
import { generateClient } from "aws-amplify/data";	 
import type { Schema } from "@/amplify/data/resource";	 
import { useAuthenticator } from "@aws-amplify/ui-react";	 
import "./../app/app.css";	 
import { Amplify } from "aws-amplify";	 
import outputs from "@/amplify_outputs.json";	 
import "@aws-amplify/ui-react/styles.css";	 
Amplify.configure(outputs);	 
 	 
const client = generateClient<Schema>();	 
export default function App() {	 
 const { signOut } = useAuthenticator();	 
 const [todos, setTodos] = useState<Array<Schema["Todo"]["type"]>>([]);	 
 	 
 function deleteTodo(id: string) {	 
 client.models.Todo.delete({ id })	 
 }	 
 function listTodos() {	 
 client.models.Todo.observeQuery().subscribe({	 
 next: (data) => setTodos([...data.items]),	 
 });	 
 }	 
 useEffect(() => {	 
 listTodos();	 
 }, []);	 
 function createTodo() {	 
 client.models.Todo.create({	 
 content: window.prompt("Todo content"),	 
 });	 
 }	 
 return (	 
 <main>	 
 <h1>My todos</h1>	 
 <button onClick={createTodo}>+ new</button>	 
 <ul>	 
 {todos.map((todo) => (	 
 <li onClick={() => deleteTodo(todo.id)} key={todo.id}>{todo.content}</li>	 
 ))}	 
 </ul>	 
 <div>	 
 🥳 App successfully hosted. Try creating a new todo.	 
 <br />	 
 <a href="https://docs.amplify.aws/nextjs/start/quickstart/nextjs-app-router-client-components/">	 
 Review next steps of this tutorial.	 
 </a>	 
 </div>	 
 <button onClick={signOut}>Sign out</button>	 
 </main>	 
 );	 
}	 

GraphQL API にCognitoユーザー認証を許可する

Cognito認証のログインUIを実装することで、ユーザーごとに認証が可能となりました。これに合わせて、バックエンドのGraphQL APIでもユーザー認証を行えるよう、コードで設定を行います。

以下のコードの強調箇所を追加してください。

import { type ClientSchema, a, defineData } from "@aws-amplify/backend";
import { helloAmplify } from "../functions/helloamplify/resource";

~~中略~~
const schema = a.schema({
  Todo: a
    .model({
      content: a.string(),
    })
    .authorization((allow) => [allow.publicApiKey()]),

  Invoices: a
    .model({
      InvoiceID: a.id().required(),
      BillNo: a.string().required(),
      SlipNo: a.string().required(),
      CustomerID: a.string().required(),
      CustomerName: a.string().required(),
      Products: a.string().required(),
      Number: a.integer().required(),
      UnitPrice: a.float().required(),
      Date: a.string().required(),
    })
    .identifier(["InvoiceID"])
    .authorization((allow) => [
      allow.publicApiKey(),
      allow.authenticated(),        // Cognitoユーザープール認証(ベアラー認証)
    ]),    
  
  helloAmplify: a
    .query()
    .returns(a.string())
    .authorization((allow) => [allow.publicApiKey()])
    .handler(a.handler.function(helloAmplify)),    
});
~~中略~~

レポートパラメータへトークン情報を設定する

これまでの実装で、GraphQL APIへのユーザー認証ができるようになりました。
この認証情報(トークン)をレポートパラメータに渡すため、ReportViewerコンポーネントやactivereportsjsviewerページにもコードを追加していきます。
以下の強調箇所をそれぞれのコードに追加してください。

'use client';

import { Core } from "@mescius/activereportsjs";  // ActiveReportsJSのCoreライブラリを読み込み、ライセンス設定などに使用
import { Viewer,Props as ViewerProps} from "@mescius/activereportsjs-react"; // ActiveReportsJSViewerコンポーネント,Props型のインポート
import "@mescius/activereportsjs/pdfexport";      // PDFエクスポートモジュール
import "@mescius/activereportsjs-i18n";           // ローカライズモジュール

import useLicense from '../hooks/useLicense';
import React, { useEffect, useRef } from "react"; 

// ActiveReportsJSのCSS設定
import "@mescius/activereportsjs/styles/ar-js-ui.css";
import "@mescius/activereportsjs/styles/ar-js-viewer.css";


// ViewerWrapper コンポーネント
const ViewerWrapper = (props: ViewerWrapperProps) => {
  // ライセンス読み込み状態
  const isLicenseLoaded = useLicense(Core, 'activereportsjs');
  // Viewerコンポーネントにアクセスするためのrefを作成
  const viewerRef = useRef<Viewer>(null);

  useEffect(() => {
    if (isLicenseLoaded && viewerRef.current) {
      viewerRef.current.Viewer.open(props.reportUri, { ReportParams: [props.reportParams] });
    }
  }, [props.reportUri, isLicenseLoaded, props.reportParams]);

  // ライセンスが読み込まれるまでは、ローディングメッセージを表示
  if (!isLicenseLoaded) {
    return <div>Loading license...</div>;
  }

  // Viewerレンダリング
  return <Viewer {...props} ref={viewerRef} />;
};

// ViewerWrapperProps型
export type ViewerWrapperProps = ViewerProps & { reportUri: string; reportParams?: any };

// ViewerWrapperコンポーネントをデフォルトエクスポート
export default ViewerWrapper;

"use client";
import type { NextPage } from "next";
import React, { useEffect, useState } from "react";

import { fetchAuthSession } from "aws-amplify/auth";
import type { ViewerWrapperProps } from "../../components/ReportViewer";


// 動的インポートを使用して、レポートビューワのラッパーをロードします。詳細については、「https://nextjs.org/docs/advanced-features/dynamic-import」を参照してください。
import dynamic from "next/dynamic";
const Viewer = dynamic<ViewerWrapperProps>(
  async () => {
    return (await import("../../components/ReportViewer")).default;
  },
  { ssr: false }
);

const Home: NextPage = () => {
  const [token, setToken] = useState<string | null>(null);

  useEffect(() => {
    const fetchToken = async () => {
      try {
        const session = await fetchAuthSession();
        // fetchAuthSession()の返り値からidTokenまたはaccessTokenを取得
        const accessToken = session.tokens?.accessToken?.toString();
        setToken(accessToken ?? null);
        console.log("Access Token:", accessToken);
      } catch (e) {
        setToken(null);
      }
    };
    fetchToken();
  }, []);

  return (
    <div
      style={{ width: "100%", height: "100vh" }}
    >
      <Viewer
        reportUri="reports/Invoice_green.rdlx-json"
        language="ja"
        reportParams={{ Name: "Bearer", Value: token }}
      />
    </div>
  );
};

export default Home;

アプリケーションの実行

ここまでの実装が完了しましたら、動作確認を行っていきます。

ライセンスキーの設定

まず、ActiveReportsJSのライセンスキーを「.env.local」に設定します。設定済みのWijmoの環境変数の下に、変数名「ACTIVEREPORTSJS_LICENSE_KEY」として追加してください。

WIJMO_LICENSE_KEY = "取得した開発用ライセンスキー" 
ACTIVEREPORTSJS_LICENSE_KEY = "取得した開発用ライセンスキー"

また、Amplifyのクラウド環境で環境変数を使うため、プロジェクト直下の「amplify.yml」に以下の設定を追加し、環境変数を読み込めるようにします。

version: 1
backend:
  phases:
    build:
      commands:
        - npm ci --cache .npm --prefer-offline
        - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
frontend:
  phases:
    build:
      commands:
        - npm run build
        - echo "WIJMO_LICENSE_KEY=$WIJMO_LICENSE_KEY" >> .env
        - echo "ACTIVEREPORTSJS_LICENSE_KEY=$ACTIVEREPORTSJS_LICENSE_KEY" >> .env
  artifacts:
    baseDirectory: .next
    files:
      - '**/*'
  cache:
    paths:
      - .next/cache/**/*
      - .npm/**/*
      - node_modules/**/*

ローカル環境で動作確認

それでは、ローカル環境で動作確認を行ってみます。まず、Amplifyのサンドボックスを実行するためAWS CLIでのシングルサインオン(SSO)認証設定を行います。

aws configure sso

認証情報の設定後、以下のコマンドにてサンドボックスを作成します。

npx ampx sandbox --profile <認証情報を設定したプロファイル 例:amplify-policy-xxxxxx>

サンドボックスの作成後、上書きされた「amplify_outputs.json」のdataのエンドポイントをレポートファイルのデータソースのエンドポイントへ指定します。

さらに、別のターミナルを開き、以下のコマンドでアプリケーションを実行します。

npm run dev

実行すると、ログイン画面が表示されます。初回はアカウントを作成し、そのアカウントでログインしてください。ログイン後、「http://localhost:3000/activereportsjsviewer」にアクセスすると、GraphQL APIをデータソースとした帳票が正しく表示されます。

また、「http://localhost:3000/flexgrid」にアクセスすると、Wijmoで作成したグリッド形式の入力画面が表示されます。ここでも、帳票と同じ内容が表示されていることが確認できます。

Amplifyにデプロイ

GitHubにコードをプッシュすると、自動的にAWS Amplifyでデプロイが開始されます。そのため、Amplify上でも正しく動作する状態でGitコミット・プッシュを行う必要があります。

今回使用しているActiveReportsJSでは、製品を利用するためにライセンスキーの設定が必要です。ローカルの開発環境とAmplifyなどの実行環境では、それぞれ異なるライセンスキーを設定する必要があります。

ローカル環境にはすでに環境変数を追加しており、Amplifyクラウド環境で環境変数を使用するための「amplify.yml」も構成済みです。あとは、実際にAmplifyクラウド環境へ環境変数を追加します。手順は以下の記事で紹介した内容と同様です。

環境変数の追加

最後に、レポートファイルのデータソースのエンドポイントとして、Amplifyクラウド環境の「amplify_outputs.json」に記載されているdataのエンドポイントを指定し、Gitコミット後にGitHubへコードをプッシュします。

GitHubにプッシュすると、自動的にAWS Amplifyでデプロイが開始され、「デプロイ済み」と表示されればデプロイ完了です。

デプロイ成功

デプロイしたアプリの動作確認

さいごに、「https://デプロイ先のURL/activereportsjsviewer」にアクセスして動作確認を行います。GraphQL APIから取得したAmplifyクラウド環境上のDynamoDBの内容が、請求書帳票上に反映されており、正しくデプロイされていることが確認できます。

さいごに

今回は、Amplify Gen2を活用したサーバーレス構成のWebアプリにActiveReportsJSを組み込む手順を解説しました。Amplify Gen2で自動生成されるバックエンドのGraphQL API(AppSync)を活用することで、Web APIの実装が不要になり、さらにAWSの認証機能を利用することで非常にセキュアで簡単に実装できます。クラウドベースのシステム開発やサーバーレス環境の導入を検討されている方は、ぜひ本記事を参考にしていただけると幸いです。

今回ご紹介したソースコードはGitHubで公開しています。こちらも是非ご確認ください。

製品サイトでは、今回ご紹介したActiveReportsJSの機能を手軽に体験できるデモアプリケーションやトライアル版も公開しておりますので、こちらもご確認ください。

また、ご導入前の製品に関するご相談、ご導入後の各種サービスに関するご質問など、お気軽にお問合せください。

\  この記事をシェアする  /