基本はDynamoDBのようなNoSQLドキュメントなので、joinしなくてもいい(非正規化)、自己完結型のデータ設計をしていくのが良い。
SELECT文(WHERE, ORDER BY)や簡単な集計関数が使える。
このCosmos DBをNode.jsのSDKを使って一通り触っていく。
$ npm install @azure/cosmos
RDBと同じように、データベースがあり、その中にコンテナ(テーブルみたいなもの)がある。
const CosmosClient = require("@azure/cosmos").CosmosClient;
const endpoint = "your cosmosdb endpoint"
const key = "your cosmosdb primary key"
// client初期化
const client = new CosmosClient({ endpoint, key });
// DB選択
const database = client.database(databaseId);
// コンテナーを選択
const container = database.container(containerId);
// コンテナ内全取得
const querySpec = {
query: "SELECT * from c"
};
const { resources: items } = await container.items
.query(querySpec)
.fetchAll();
// 新規作成
const { resource: createdItem } = await container.items.create(newItem);
// 更新
const { id, category } = createdItem;
createdItem.isComplete = true;
const { resource: updatedItem } = await container
.item(id, category)
.replace(createdItem);
// 削除
const { resource: result } = await container.item(id, category).delete();
より詳細な使い方は公式を参照
以下設定でcosmosDB作成
- Azure portal のメニューまたは [ホーム] ページで、 [リソースの作成] を選択します。
- [新規] ページで、 [Azure Cosmos DB] を検索して選択します。
- [Azure Cosmos DB] ページで、 [作成] を選択します。
- [Azure Cosmos DB アカウントの作成] ページで、新しい Azure Cosmos アカウントの基本的な設定を入力します。
本記事を執筆時には以下の設定でDBを作成した。
基本:
場所: 東日本
アカウント名: (新規) {アカウント名を入力}
API: Core (SQL)
Account Type: Non-Production
geo 冗長性: 無効
マルチ リージョン書き込み: 有効
Availability Zones: 無効
ネットワーク:
Connectivity methodAll: networks
- [Review + create](レビュー + 作成) を選択します。 [ネットワーク] セクションと [タグ] セクションはスキップできます。
- アカウントの設定を確認し、 [作成] を選択します。 アカウントの作成には数分かかります。 ポータル ページに "デプロイが完了しました" と表示されるまで待ちます。
- [リソースに移動] を選択し、Azure Cosmos DB アカウント ページに移動します。
- Azure Cosmos DB アカウント ページに移動し、 [キー] を選択します。 次に作成する Web アプリケーションで使用する値をコピーします。
$ npm install express-generator -g
$ express todo
$ cd todo
$ npm install
$ npm start
ここまで入力してブラウザでhttp://localhost:3000/を開くとExpressの初期画面が見える
$ npm install @azure/cosmos
$ mkdir model
models/taskDao.jsを作成し以下を貼り付け
const CosmosClient = require('@azure/cosmos').CosmosClient
const debug = require('debug')('todo:taskDao')
// For simplicity we'll set a constant partition key
const partitionKey = undefined
class TaskDao {
/**
* Manages reading, adding, and updating Tasks in Cosmos DB
* @param {CosmosClient} cosmosClient
* @param {string} databaseId
* @param {string} containerId
*/
constructor(cosmosClient, databaseId, containerId) {
this.client = cosmosClient
this.databaseId = databaseId
this.collectionId = containerId
this.database = null
this.container = null
}
async init() {
debug('Setting up the database...')
const dbResponse = await this.client.databases.createIfNotExists({
id: this.databaseId
})
this.database = dbResponse.database
debug('Setting up the database...done!')
debug('Setting up the container...')
const coResponse = await this.database.containers.createIfNotExists({
id: this.collectionId
})
this.container = coResponse.container
debug('Setting up the container...done!')
}
async find(querySpec) {
debug('Querying for items from the database')
if (!this.container) {
throw new Error('Collection is not initialized.')
}
const { resources } = await this.container.items.query(querySpec).fetchAll()
return resources
}
async addItem(item) {
debug('Adding an item to the database')
item.date = Date.now()
item.completed = false
const { resource: doc } = await this.container.items.create(item)
return doc
}
async updateItem(itemId) {
debug('Update an item in the database')
const doc = await this.getItem(itemId)
doc.completed = true
const { resource: replaced } = await this.container
.item(itemId, partitionKey)
.replace(doc)
return replaced
}
async getItem(itemId) {
debug('Getting an item from the database')
const { resource } = await this.container.item(itemId, partitionKey).read()
return resource
}
}
module.exports = TaskDao
routes/tasklist.jsを作成し、以下を貼り付け
const TaskDao = require("../models/TaskDao");
class TaskList {
/**
* Handles the various APIs for displaying and managing tasks
* @param {TaskDao} taskDao
*/
constructor(taskDao) {
this.taskDao = taskDao;
}
async showTasks(req, res) {
const querySpec = {
query: "SELECT * FROM root r WHERE r.completed=@completed",
parameters: [
{
name: "@completed",
value: false
}
]
};
const items = await this.taskDao.find(querySpec);
res.render("index", {
title: "My ToDo List ",
tasks: items
});
}
async addTask(req, res) {
const item = req.body;
await this.taskDao.addItem(item);
res.redirect("/");
}
async completeTask(req, res) {
const completedTasks = Object.keys(req.body);
const tasks = [];
completedTasks.forEach(task => {
tasks.push(this.taskDao.updateItem(task));
});
await Promise.all(tasks);
res.redirect("/");
}
}
module.exports = TaskList;
プロジェクトルートにconfig.jsを作成し以下を貼り付け
const config = {};
config.host = process.env.HOST || "[the endpoint URI of your Azure Cosmos DB account]";
config.authKey =
process.env.AUTH_KEY || "[the PRIMARY KEY value of your Azure Cosmos DB account";
config.databaseId = "ToDoList";
config.containerId = "Items";
if (config.host.includes("https://localhost:")) {
console.log("Local environment detected");
console.log("WARNING: Disabled checking of self-signed certs. Do not have this code in production.");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
console.log(`Go to http://localhost:${process.env.PORT || '3000'} to try the sample.`);
}
module.exports = config;
リソース作成時にメモしたURLをhostに、PRIMARY_KEYをauthKeyに記述
views/layout.jadeを以下に書き換え
doctype html
html
head
title= title
link(rel='stylesheet', href='//ajax.aspnetcdn.com/ajax/bootstrap/3.3.2/css/bootstrap.min.css')
link(rel='stylesheet', href='/stylesheets/style.css')
body
nav.navbar.navbar-inverse.navbar-fixed-top
div.navbar-header
a.navbar-brand(href='#') My Tasks
block content
script(src='//ajax.aspnetcdn.com/ajax/jQuery/jquery-1.11.2.min.js')
script(src='//ajax.aspnetcdn.com/ajax/bootstrap/3.3.2/bootstrap.min.js')
views/index.jadeを以下に書き換え
extends layout
block content
h1 #{title}
br
form(action="/completetask", method="post")
table.table.table-striped.table-bordered
tr
td Name
td Category
td Date
td Complete
if (typeof tasks === "undefined")
tr
td
else
each task in tasks
tr
td #{task.name}
td #{task.category}
- var date = new Date(task.date);
- var day = date.getDate();
- var month = date.getMonth() + 1;
- var year = date.getFullYear();
td #{month + "/" + day + "/" + year}
td
if(task.completed)
input(type="checkbox", name="#{task.id}", value="#{!task.completed}", checked=task.completed)
else
input(type="checkbox", name="#{task.id}", value="#{!task.completed}", checked=task.completed)
button.btn.btn-primary(type="submit") Update tasks
hr
form.well(action="/addtask", method="post")
label Item Name:
input(name="name", type="textbox")
label Item Category:
input(name="category", type="textbox")
br
button.btn(type="submit") Add item
再度npm startを実行するとタスク管理アプリが起動する。