Dapps開発の基本

概要

Dappsの本格的な開発に入る前に、まず開発の流れを把握する意味でブラウザに「Hello World!」と表示する、簡単なDappsを開発してみたいと思います。

開発環境

  • Solidity
  • Truffle
  • Ganache
  • Metamask

開発の流れ

  1. プロジェクトの構築
  2. コントラクトの作成
  3. コントラクトのコンパイル
  4. コントラクトのデプロイ
  5. コントラクトのテスト
  6. フロントエンドの構築

プロジェクトの構築

hello-worldプロジェクトを作成し、truffle initで初期化します。

$ mkdir hello-world
$ cd hello-world
$ truffle init

hello-worldディレクトリは以下の様な構造になります。

├── contracts
├── migrations
├── test
└── truffle-config.js

コントラクトの作成

コントラクトファイルを作成します。

$ truffle create contract HelloWorld

コントラクトの内容はget()メソッドで「HelloWorld!」を返す簡単なコントラクトです。

pragma solidity ^0.5.0;

contract HelloWorld {
  // string型の変数wordを定義
  string word;

  // 起動時にword変数に「Hello World!」を入れる
  constructor() public {
    word = 'Hello World!';
  }

  // get()関数はword変数を返す
  function get() public view returns (string memory) {
    return word;
  }
}

コントラクトのコンパイル

作成したHelloWorld.solをコンパイルします。 コンパイルするとbuildファイルが作成されます。

$ truffle compile

コントラクトのデプロイ

コントラクトをネットワークにデプロイします。その為にマイグレーションファイルを作成します。

$ truffle create migration HelloWorld

すると1××××_deploy_contract.jsと言うファイルがmigrationsディレクトリの中に作成されます。 マイグレーションファイルの内容は以下の通りにします。

let HelloWorld = artifacts.require("HelloWorld")
module.exports = function(deployer) {
  deployer.deploy(HelloWorld);
};

作成したマイグレーションファイルを使ってブロックチェーンにデプロイする為にローカルでプライベートチェーンを生成するGanacheを起動します。

f:id:adrenaline2017:20190707084302p:plain

Ganacheの設定に合わせる為にtruffle-config.jsを編集します。

module.exports = {
    networks: {
         development: {
         host: "127.0.0.1",     // Localhost (default: none)
         port: 7545,            // Standard Ethereum port (default: none)
         network_id: "*",       // Any network (default: none)
        },
},

デプロイは以下の通りです。

$ truffle migrate

フロントエンドの構築

htmlとjsファイルの作成します。ファイルを作成するディレクトリの構成は以下の通りになります。

├── build
├── contracts
├── hello_world.js
├── index.html
├── migrations
├── test
└── truffle-config.js

index.htmlは以下の通りです。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>Ðapps - Hello World</title>
    <script type="text/javascript" src="hello_world.js"></script>
  </head>
  <body>
    <div id="contract_result">loading...</div>
  </body>
</html>

hello_world.jsは以下の通りです。

var abi = [
  {
    "constant": true,
    "inputs": [],
    "name": "get",
    "outputs": [
      {
        "name": "",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "pure",
    "type": "function",
    "signature": "0x6d4ce63c"
  }
];
var address = "0xd6856EEB9f6B3934E5925414e2c9D911a567e4c3";
   
  window.onload = function() {
    var contract = web3.eth.contract(abi).at(address);
    contract.get((error, result) => {
      document.getElementById("contract_result").textContent = result;
    });
  };

Metamaskを起動します。Metamaskを使うことで、ブラウザでブロックチェーンとやりとりすることができるようになります。Ganacheと接続する為に、Metamaskの設定はGanacheの設定に合わせる必要があります。 f:id:adrenaline2017:20190707230902p:plain

しかし、Metamaskはweb3オブジェクトを生成しないので、このままでは画面に表示することが出来ません。そこで今回は簡易サバーとしてlive-serverを使用します。live-serverはブラウザの自動更新をしてくれる、簡易ローカルサーバーを起動できるツールです。

$ npm i -g live-server
$live-server

ブラウザが起動して画面に「Hello World!」と表示されれば成功です。

これでdappsを開発する流れが一通り掴めたかと思います。 次回は本格的なdappsの開発のチュートリアルに挑戦してみたいと思います。

Controller

コントローラ

コントローラの役目は、HTTPリクエストを受け取りレスポンスを返すことです。

コントローラの目的は、アプリケーションの特定の要求を受け取ることです。 ルーティングによりコントローラがどのHTTPリクエストを受け取るかを制御します。下記の図の様に、各コントローラには複数のルートがあり、異なるルートで異なるアクションを実行することが可能です。

f:id:adrenaline2017:20191130163225p:plain

基本的なコントローラを作成するために、クラスとデコレータを使用します。 デコレータはクラスに必要なメタデータに関連付け、Nestがルーティングマップを作成できるようにします。

import { Get, Controller } from '@nestjs/common';
import { AppService } from './app.service';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  root(): string {
    return this.appService.root();
  }
}

ルーティング

ルーティングはデータをクライアントまで送信するために、コンピュータネットワーク上のデータの経路を決定する制御のことです。

次の例では、@Controllerデコレータを使用して基本的なコントローラの定義をします。@Controller(’cats’)デコレータの'cats'の部分にルートを宣言することで”http://localhost:3000/cats”へのルーティングが定義されます。以下のコマンドを実行する事でcatsコントローラを作成して見ましょう。

$nest g controller cats

cats.controller.ts

import { Controller, Get } from '@nestjs/common';

//catsを追加
@Controller('cats')
export class CatsController {
  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

@Getデコレータは、対応するリクエストに@Get以下の内容をマップ(関連づける)するよう、Nestに指示します。

リクエストオブジェクト

多くの端末では、クライアントのリクエストの詳細にアクセスする必要があります。Nestではライブラリ固有のリクエストオブジェクトを使用しています。@ Reqデコレータを使用してNestにリクエストオブジェクト(@Req)をハンドラ(@Getの中)に入れることができます。

cats.controller.ts

import { Controller, Get, Req } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Get()
  findAll(@Req() request) {
    return 'This action returns all cats';
  }
}

リクエストオブジェクトはHTTPリクエストを表し、クエリ文字列、パラメータ、HTTPヘッダー、および本文のプロパティを持っています。@Bodyや@Queryなどの専用のデコレータを使用することができます。以下は、デコレータとオブジェクトの比較です。

デコレーター オブジェクト
@Request() req
@Response() res
@Next() next
@Session() req.session
@Param(param?: string) req.params / req.params[param]
@Body(param?: string) req.body / req.body[param]
@Query(param?: string) req.query / req.query[param]
@Headers(param?: string) req.headers / req.headers[param]

HTTPリクエストオブジェクト

catsリソースを取得するためのGETリクエストを定義しました。次にPOSTハンドラを作成しましょう:

cats.controller.ts

import { Controller, Get, Post } from '@nestjs/common';

@Controller('cats')
export class CatsController {

  @Post()
  create() {
    return 'This action adds a new cat';
  }

  @Get()
  findAll() {
    return 'This action returns all cats';
  }
}

これらすべては其々のHTTPリクエストメソッドを表します。

メソッド 役割
@Get() リソースの取得
@Post 子リソースの作成、リソースへのデータ追加
@Put() リソースの更新、リソースの作成
@Delete() リソースの削除
@Patch() リソースの一部を更新
@options() リソースがサポートしているメソッドの取得
@Head() リソースのヘッダ (メタデータの取得)

ルートのワイルドカード

パターンベースのルートもサポートされています。 たとえば、アスタリスクワイルドカード(任意の文字を指示するための特殊な文字記号)として使用され、任意の文字の組み合わせに一致します。

@Get('ab*cd')
findAll() {
  return 'This route uses a wildcard';
}

上の経路パスは、abcd、ab_cd、abecdなどを表しています。 文字 ? + * ()は正規表現の部分集合です。 ハイフン( - )とドット(。)は、文字列ベースのパスによってそのままの意味で解釈されます。

ステータスコード

レスポンスされるHTTPステータスコードはデフォルトで200ですが、POSTレスポンスでは201以外のものです。ハンドラレベルで@HttpCodeデコレータを追加することで、この動作を簡単に変更できます。

@Post()
@HttpCode(204)
create() {
  return 'This action adds a new cat';
}

多くの場合、ステータスコードは静的ではありませんが、さまざまな要因によって異なります。 その場合、ライブラリ固有のレスポンス(@Resを使用して挿入)オブジェクトを使用できます(エラーの場合は例外をスローします)。

ヘッダー

カスタムレスポンスヘッダを指定するには、@Headerデコレータまたはライブラリ固有のレスポンスオブジェクトを使用できます。

@Post()
@Header('Cache-Control', 'none')
create() {
  return 'This action adds a new cat';
}

ルートパラメータ

URLの一部として動的データを受け入れる必要がある場合、静的パスのルートは役立ちません。 パラメータで経路を定義するために、ルートにおけるルートパラメータを直接的に特定することができます。

@Get(':id')
findOne(@Param() params) {
  console.log(params.id);
  return `This action returns a #${params.id} cat`;
}

特定のパラメータを取得するには、その名前をかっこで渡します。

@Get(':id')
findOne(@Param('id') id) {
  return `This action returns a #${id} cat`;
}

Async/await

現代のJavaScriptのデータの抽出はほとんど非同期で行われます。 そのため、Nestはasync関数をサポートしています。

すべてのasync関数はPromiseを返す必要があります。 これは、Nest自身で解決できる遅延値を返すことができることを意味します。 下の例を見てみましょう。

cats.controller.ts

@Get()
async findAll(): Promise<any[]> {
  return [];
}

上記のコードは有効です。 さらにNestのルートハンドラは、RxJS(イベントのハンドリングや非同期処理をラップするライブラリ)がデータを流れとして返すことによりパワフルに動作します。 Nestは自動的に下のソースを一つずつ取り出し、最初に出力された値を取得します。

cats.controller.ts

@Get()
findAll(): Observable<any[]> {
  return of([]);
}

リクエスペイロード

ペイロード:ヘッダやトレーラなどの付加的情報を除いた、データ本体のこと

前のPOSTルートハンドラ(指定したURLアドレスをリクエストすると、コールバックされる)の例では、クライアントパラメータは受け付けていませんでした。 @Body()引数をここに追加して修正します。

しかし、最初に(TypeScriptを使用する場合)DTOの構造を決定する必要があります。 DTOはデータがネットワークを介してどのように送信されるかを定義するオブジェクトです。 TypeScriptインターフェイスを使用するか、単純なクラスを使用してDTOスキーマを判断できます。 驚いたことに、ここでクラスを使用することをお勧めします。

クラスはJavaScript ES6標準の一部です。したがって、それらは単純な関数を表します。 一方TypeScriptインターフェイスが削除されるため、Nestはそれらを参照できません。 これは、パイプのような機能が変数のメタタイプにアクセスするときに追加の可能性を可能にするため重要なのです。

CreateCatDtoクラスを作成しましょう:

creare-cat.dto.ts

export class CreateCatDto {
  readonly name: string;
  readonly age: number;
  readonly breed: string;
}

基本的なプロパティは3つしかありません。 できるだけ純粋なものとして機能させるように常に努めなければならないので、それらはすべて読み取り専用としてマークされています。 その後、CatsController内で新しく作成したスキーマを使用できます。

cats.controller.ts

@Post()
async create(@Body() createCatDto: CreateCatDto) {
  return 'This action adds a new cat';
}

サンプル

いくつかのデコレータを使用して基本コントローラを作成する例を示します。 次のコントローラは、内部データにアクセスして操作するための2つのメソッドを公開しています。

cats.controller.ts

import { Controller, Get, Post, Body, Put, Param, Delete } from '@nestjs/common';

@Controller('cats')
export class CatsController {
  @Post()
  create(@Body() createCatDto) {
    return 'This action adds a new cat';
  }

  @Get()
  findAll(@Query() query) {
    return `This action returns all cats (limit: ${query.limit} items)`;
  }

  @Get(':id')
  findOne(@Param('id') id) {
    return `This action returns a #${id} cat`;
  }

  @Put(':id')
  update(@Param('id') id, @Body() updateCatDto) {
    return `This action updates a #${id} cat`;
  }

  @Delete(':id')
  remove(@Param('id') id) {
    return `This action removes a #${id} cat`;
  }
}

エラーの処理

エラー処理については別の章があります。

立ち上げと実行

上記のコントローラはNestはCatsControllerの存在をまだ知りません。ですので、このクラスのインスタンスは作成されません。

コントローラは常にモジュールに属します。なぜなら、@Moduleデコレータ内にコントローラ配列を保持しているからです。 ルートApplicationModule以外のモジュールはないので、これを使ってCatsControllerを導入します:

app.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats/cats.controller';

@Module({
  controllers: [CatsController],
})
export class ApplicationModule {}

私たちはモジュールクラスにメタデータを添付しました。これでNestはどのコントローラをマウントする必要があるのかを簡単に反映することができます。

Solidityの構文

Solidityとは

f:id:adrenaline2017:20190706101609p:plain Ethereumでスマートコントラクトを開発する為の言語です。構文がJavaScriptに似てるので、非常に親みやすい言語だと思います。 solidityはsolcと呼ばれるSolidityコンパイラによってEVM(Ethereum Virtual Machine)が扱える専用のバイトコードに変換されます。

solidityの型には値型と参照型があります。値型は実データそのものを格納し、参照型は実データを参照するためのポインタを格納します。

値型

  • 符号付き整数(int)
  • 符号なし整数(uint)
  • 文字列(string)
  • 論理型(boolean)
  • アドレス(address)
  • バイト型配列(bytes)
  • 関数 (function)
  • 列挙型(enum)

参照型

符号付き整数/符号なし整数

整数には 符号あり整数(int)と符号なし整数(uint)があります。intとuintは変数のbit長を8-256bitで指定出来ます。例えば8bitのuintの場合はuint8と表します。

    // uintに-60を代入するとは出来ない
    uint signedInteger = 60;
    int unsignedInteger = -30;

加算、減算、乗算、徐算、剰余は他のプログラミング言語と同じ様に出来ます。またビット演算子(&, |, ^, ~, <<, >>)比較(<=, <, ==, >, >=)論理演算子(!, &&, ||, ==, !=)計算代入も使用することが可能です。

    uint base = 60;
    uint add = base + 10; // 70
    uint sub = base - 10; // 50
    uint mul = base * 10; // 600
    uint div  = base / 10; //6

文字列

stringには文字列を入れることができます。しかしsolidityのstringの場合、そのまま文字列を比較することができません。Keccak256関数を使って文字列をbytes32型にしてから比較する必要があります。

  
function comparison(string memory _str) public pure returns (bool){
        return keccak256(abi.encode(_str)) == keccak256(abi.encode("hoge"));
    }

アドレス

ウォレットアドレスとコントラクトアドレスがあります。Ethereumのアドレスサイズである20bytesの値を格納します。

function getBalance(address _addr)public view returns (uint){
        return _addr.balance;
}

関数

関数の宣言はfunctionを使います。

//function 関数名(型 変数名){関数の処理}

function companyMember(string _name, uint _age){}

関数を呼び出す場合は以下の通りです。

companyMember("jiro", "37");

関数から戻り値を返す時はアクセス修飾子の後ろにreturns、処理にreturnを書きます。returnsの後には戻り値の型を宣言します。

//function 関数名 (型 変数名)アクセス修飾子 returns(戻り値の型){} 

string word = "Hello World!";
function helloWorld() public view returns (string memory){
    return word;
  } 

他からのアクセスをコントロールする為にsolidutyではpublic, private, internal, externalの4つのアクセス修飾子があります。

・public: コントラクト内外部及び継承されるのも可能

・private: コントラクト内部からのみ参照が可能

・internal: コントラクト内部及び継承されれるのが可能

・external: コントラクト外部からのみ参照が可能

状態修飾子は関数がブロックチェーンとどのように作用し合うのか示すもので、viewとpureの2種類があります。どちらもトランザクションを発行しないので、呼び出しの際にガスは必要ありません。

・view ブロックチェーンに対してデータの保存や変更は一切行わず、読み取りだけを行います。

・pure ブロックチェーンに対してデータの保存や変更を一切行わないだけでなく、読み取りも行いません。

solidityではデータの保存にstorageとmemoryの2種類があります

・storage:ブロックチェーンにデータを永久に保存するのでgathコストがかかる

・memory: コントラクト実行時に一時的にデータを保持するだけなのでgathコストがかからない

privateの場合は関数名の前にアンダーバー(addToArray)が慣習的に使われます。書かなくてもコンパイルエラーにはなりません。memberのアンダーバーは引数が関数内で参照される際に慣習的に使われます)

uint[] members;

function addToArray(uint _member) private {
    members.push(_member);
}

列挙型

列挙型はユーザー定義型を作成する方法です。列挙型には少なくとも1つの定数が必要です。

contract Enum{
    enum Colors{
        Red,
        Blue,
        Green
    }
    Colors color;
    
    function setColor() public {
        color = Colors.Blue;
    }

構造体

複雑なデータ型を作る際に構造体(struct)を用いて表現することが出来ます。 構造体には複数のプロパティを持つデータ型を作ることが可能です。

contract Structure {
    struct Person {
        string name;
        uint age;
    }
    
    Person[] public projects;
    
    function createPerson(string memory _name, uint _age)public{
        Person memory newPerson = Person({
           name: _name,
           age: _age
        });
        projects.push(newPerson);
    }
}

配列

solidityでは2種類の配列があります。

・固定配列 サイズを指定した固定長になります。

uint[2] fixedArray;

・可変配列 サイズが指定されていない可変長になります。

uint[] dynamicArray;

マッピング

マッピングはデータとデータを参照するキーを組み合わせたKVS(キーバリューストア)です。

//mapping (キー => バリュー) アクセス修飾子 マッピング名;

contract Account{
    struct User {
        address addr;
        uint amount;
    }
    mapping(uint => User) public Users;
}

Module

モジュール

モジュールはコントローラやサービスなどプロパティの集まりで、@Moduleデコレータが付けられたクラスです。 @Moduleデコレータは、Nestがアプリケーション構造を整理するために使用します。

f:id:adrenaline2017:20191130162643p:plain

各アプリケーションには少なくとも1つのルートモジュールがあります。 ルートモジュールは、ツリーの頂点に位置するモジュールです。アプリケーションが小さい場合 、ルートモジュールがアプリケーション内の唯一のモジュールになりますが、大規模なアプリケーションではいくつかのモジュールがあり、それぞれが密接に関連します。

@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})

export class AppModule {}
imports このモジュールで必要とするインポートされたモジュールのリスト
controllers 作成しなければならないコントローラ
providers コントローラ以外のクラス(サービスなど)を記載
exports 他のモジュールで利用できるプロバイダの一部

モジュールは、デフォルトでプロバイダをカプセル化します。 つまりこれはモジュールの一部でなく、インポートされたモジュールからエクスポートされていないプロバイダを注入することは出来ないこと意味します。

フィーチャーモジュール(モジュールのimports)

CatsControllerとCatsServiceは同じアプリケーションに属します。 フィーチャモジュールであるCatsModuleに移動します。

cats/cats.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {}

ヒント モジュールを作成するには$ nest g module catsコマンドを実行します。 cats.module.tsファイルを定義した後、このモジュールに関連するすべてをcatsディレクトリに移動しました。 最後に、このモジュールをルートモジュール(ApplicationModule)にインポートする必要があります。

app.module.ts

import { Module } from '@nestjs/common';
import { CatsModule } from './cats/cats.module';

@Module({
  imports: [CatsModule],
})
export class ApplicationModule {}

ApplicationModuleのほかにCatsModuleも登録することが不可欠になります。

共有モジュール(モジュールのexports)

モジュールは任意のプロバイダの同じインスタンスをモジュール間で簡単に共有できます。

f:id:adrenaline2017:20191130162835p:plain

すべてのモジュールは共有モジュールです。 作成後はどのモジュールでも再利用できます。 他のいくつかのモジュール間でCatsServiceインスタンスを共有したいと考えてみましょう。 そのためには、以下のようにCatsServiceをexporotsで配置する必要があります。

cats.module.ts JS

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

CatsModuleをインポートする各モジュールは、CatsServiceへのアクセス権を持ちます。このモジュールをインポートする全てのモジュールと同じインスタンスを共有します。

モジュールの再エクスポート

モジュールは内部プロバイダをエクスポートできます。 さらに、彼ら自身がインポートしたモジュールをエクスポートすることもできます。

@Module({
  imports: [CommonModule],
  exports: [CommonModule],
})
export class CoreModule {}

依存性注入(DI)

モジュールクラスは、プロバイダを注入することもできます:

cats.module.ts

import { Module } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Module({
  controllers: [CatsController],
  providers: [CatsService],
})
export class CatsModule {
  //ここでプロバイダを注入
  constructor(private readonly catsService: CatsService) {}
}

ただし、モジュールクラスはプロバイダによってインジェクトできません。

グローバルモジュール

同じモジュールをどこにでもインポートする必要がある場合は、グローバルモジュールにする必要があります。 Angularでは、プロバイダはグローバルスコープに登録されています。なので定義されると、どこでも利用できます。 一方、Nestはモジュールスコープ内のプロバイダをカプセル化されます。 モジュールのプロバイダをインポートせずに他の場所で使用することはできません。 しかし、時には、ヘルパー、データベース接続など、いつでもすぐに利用できる様にしたいこともあります。 そのために、モジュールを@Global()デコレータでグローバルなものにすることができます。

import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';

@Global()
@Module({
  controllers: [CatsController],
  providers: [CatsService],
  exports: [CatsService]
})
export class CatsModule {}

@Global()デコレータは、モジュールをグローバルスコープにします。 グローバルモジュールは、ルートモジュールまたはコアモジュールによって1回だけ登録することが望ましいです。

ヒント 単純に全てをグローバルにすることは良いことではありません。 グローバルモジュールは、必要な定型文の量を減らすために利用できます。 インポート配列は、モジュールAPIを透過的にする最も良い方法です。

ダイナミックモジュール

Nestのモジュールシステムには、動的モジュールと呼ばれる機能があります。 これは、努力せずにカスタマイズ可能なモジュールを作成することができます。 DatabaseModuleを見てみましょう:

import { Module, DynamicModule } from '@nestjs/common';
import { createDatabaseProviders } from './database.providers';
import { Connection } from './connection.provider';

@Module({
  providers: [Connection],
})
export class DatabaseModule {
  static forRoot(entities = [], options?): DynamicModule {
    const providers = createDatabaseProviders(options, entities);
    return {
      module: DatabaseModule,
      providers: providers,
      exports: providers,
    };
  }
}

ヒント forRoot()は、動的に同期または非同期のいずれかのモジュールを返します(Promise)。

このモジュールは、デフォルトでConnectionプロバイダを定義しますが、渡されたオプションやエンティティに応じて、リポジトリなどのプロバイダのコレクションを公開します。 実際、動的モジュールは基本モジュールのメタデータを拡張します。 この実質的な機能は、プロバイダを動的に登録する必要がある場合に便利です。 次に、次のようにDatabaseModuleをインポートできます。

import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [
    DatabaseModule.forRoot([User]),
  ],
})
export class ApplicationModule {}

動的モジュールをエクスポートするには、関数呼び出し部分を省略することができます。

import { Module } from '@nestjs/common';
import { DatabaseModule } from './database/database.module';
import { User } from './users/entities/user.entity';

@Module({
  imports: [
    DatabaseModule.forRoot([User]),
  ],
  exports: [DatabaseModule]
})
export class ApplicationModule {}

NestJS

NestJSとは?

Node.js向けWebアプリケーションフレームワーク(フルスタックフレームワーク)です。

f:id:adrenaline2017:20191209104727p:plain

構成

NestJSの基本構成は以下の通りです。

①メイン(main.ts)

起動する際に一番はじめに読み込まれるメインファイルになります。

②モジュール(app.module.ts)

コントローラとサービスを受け取る器の様なものです。

③コントローラー(app.controller.ts)

リクエストを受け取りレスポンスを返します。

④プロバイダ(app.service)

機能やデータを扱うビジネスロジックを定義します。

①メイン(main.ts)

アプリを起動させる時に一番はじめに読み込まれるファイルです。 NestFactory.createに引数としてモジュールを渡し、Nestアプリケーションのインスタンスを作成します。作成したインスタンスを使ってポート3000で接続を待ち受けます。

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
}
bootstrap();

②モジュール(app.module.ts)

使用するコントローラとサービスをモジュールで受け取ります。 アプリケーションのルートモジュールであるAppModuleを定義します。

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';

// モジュールの構成を宣言
@Module({
  imports: [],
  controllers: [AppController],
  providers: [AppService],
})

// モジュールクラスの定義
export class AppModule {}

③コントローラー(app.controller.ts)

コントローラーはコントローラーデコレーター(@Controller)を使ってHTTPリクエストを受け取りレスポンスを返します。

import { Get, Controller } from '@nestjs/common';
import { AppService } from './app.service';

// 3000へのルーティングを定義している
@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

// リクエスト(./app.service)を受け取ってレスポンスを返す
@Get()
  root(): string {
    return this.appService.root();
  }
}

④プロバイダ(app.service.ts)

コンポーネントでDBとやりとりするためのORMなどをここに書きます。コントローラーにDIする為に@Injectableコンストラクタを使用します。

import { Injectable } from '@nestjs/common';

// AppServiceクラスでgetHello関数を定義
@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

Remixをgethの接続

Remixとは

f:id:adrenaline2017:20190706101334p:plain

Solidityでコントラクトを開発する為のIDEです。 Remixはローカルで立ち上げる方法とクラウドIDEの2種類があります。

gethとは

「Go Ethereum」の略で、Ethereumのフルノードを操作するためのクライアントソフトです。BitcoinでいうBitcoin coreの様なものです。

Gethの設定

$ brew tap ethereum/ethereum
$ brew install ethereum

Gethの初期化

eth_private_netフォルダにgenesis.jsonファイルを作成

$ mkdir -p /Users/ユーザー名/Documents/eth_private_net
$ cd /Users/ユーザー名/Documents/eth_private_net
$ vim genesis.json
{
"nonce": "0x0000000000000042",
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"difficulty": "0x400",
"alloc": {},
"coinbase": "0x3333333333333333333333333333333333333333",
"timestamp": "0x0",
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"extraData": "0x",
"gasLimit": "0x8000000",
"config": {}
}

Gethの初期化

geth --datadir /Users/ユーザー名/Documents/eth_private_net init /Users/ユーザー名/Documents/eth_private_net/genesis.json

Gethの起動

geth --networkid "10" --nodiscover --datadir "/Users/ユーザー名/Documents/eth_private_net" console 2>> /Users/ユーザー名/Documents/eth_private_net/geth_err.log

マイニング

アカウント作成

> personal.newAccount()

アカウントの確認

> eth.accounts

コインベースアカウントの確認

> eth.coinbase

マイニングの開始

> miner.start()

マイニングの確認

> eth.mining

アカウント1番目の残高

> web3.fromWei(eth.getBalance(eth.accounts[0]),"ether")

マイニングの停止

> miner.stop()

送金

アカウントのアンロック

personal.unlockAccount(eth.accounts[0])

アカウント1からアカウント2に5ether送金

> eth.sendTransaction({from:eth.accounts[0], to:eth.accounts[1], value:web3.toWei(5, "ether")})

Remixとgethの接続

Gethの起動

geth --networkid "10" --nodiscover --datadir "/Users/ユーザー名/Documents/eth_private_net" --mine --unlock 0 --rpc --rpcaddr "localhost" --rpcport "8545" --rpccorsdomain "*" console 2>>/Users/ユーザー名/Documents/eth_private_net/geth_err.log

マイニングの開始

> miner.start()

Remixのアクセス

Remixブラウザにアクセス

Environment項目の「Web3 Provider」を選択

Web3 Provider Endpointに「http://localhost:8545」 が設定されているのを確認し「OK」をクリック

新規にコントラクトを作成

pragma solidity ^0.4.11;
 
contract HelloAddress {
    
    address public owner;
    
    function HelloAddress(){
        owner = msg.sender;
    }
   
    function getOwnerAddress() constant returns (address){
        return owner;
    }
    
}

デプロイをクリック

「getOwnerAddress」ボタンをクリックしアドレスが表示されるか確認