祝 Nim v1.0.0 リリース!NimでAPIサーバーを書いてみる。 ORM編
祝 Nim v1.0.0 リリース!NimでAPIサーバーを書いてみる。 ORM編
ついにやってきました。
9月23日に、プログラミング言語Nimのv1.0.0がリリースされました!
v1.0.0になったことで、安定性と後方互換性が保証されるそうです。
今後、学ぶ内容が古くなって使えなくなるということはないので、
Nimを学び始めるなら、今が最も最適なタイミングだと思います。
https://nim-lang.org/blog/2019/09/23/version-100-released.html
おさらい : プログラミング言語Nimとは
- 静的型付け言語
- Pythonのインデントブロックをリスペクトした書きやすい構文
- C言語並の実行速度
- ネイティブバイナリをコンパイル可能
- javascriptにも変換できる = webのフロント開発もできる
- 強力なメタプログラミング機能(macro, template)
- ガベージコレクタあり
CやC++にトランスパイルできたり、C言語で書かれた資産をFFIで利用できたり、と
C言語との相性が非常にいいです。
実行速度が最適化したC言語並に速いのはこの辺が関係してるのかも。
ともかく、特徴だけを見れば
- 書きやすい
- モダンな言語機能を一通り揃えてる
- 実行速度も爆速
といった特徴を併せ持つ最強のプログラミング言語となっております。
(個人の感想です)
Nimでなんか書いてみよう
ということで、Nimを使って簡単なプログラムを書いてみたいと思います。
僕自身がNim初心者ということもあり、勉強の備忘録を兼ねてるので
説明がくどかったりするかもですがご了承ください。
NimでAPIサーバーを書く : ORM編
昨今、フロントエンドはReactやVueをnpm環境で開発するという方式が一般的になっています。
バックエンドはSSRしてhtmlを返すのではなく、汎用的なフォーマットであるjsonを返すのがベストプラクティスです。
ということで、NimでAPIサーバーを作りたいと思います。
まずはDBを操作するためにObject Relation Mappingをします。
nimble init : プロジェクトを作成
mkdir nim_rest
cd nim_rest
nimble init # パッケージタイプを"Binary"にして、あとはすべてデフォルト
nimbleは、nimのパッケージマネージャ兼ビルドシステムです。
Rustにおけるcargo, JSにおけるnpmやyarnみたいなもんです。
今回使うパッケージ : norm
今回使うORMパッケージ norm を説明します。
norm はNimで書かれた軽量なORMで、
バリバリにmacro, template機能を使用して実装されており
シンプルかつ少ない記述量でDB操作を行うことができます。
まずはinstall
nimble install norm
1. DBへの接続・テーブル作成
今回はDBにPostgreSQLを使います。
僕はdocker-composeで用意します。
version: "3"
services:
db:
image: postgres
ports:
- 5432:5432
environment:
POSTGRES_USER: nim
POSTGRES_PASSWORD: nim
POSTGRES_DATABASE: nim
docker-compose up -d
これで準備OK!
次に、Nimで接続のためのコードを書きます。
ついでに、モデルの定義もします。
import norm/postgres
db("localhost:5432", "nim", "nim", "nim"):
type Post* = object
title*: string
author*: string
content*: string
withDb:
createTables(force=true)
db:
このブロックでは、DBへの接続とモデルの定義を行います。
db マクロ以降の withDb ブロック内で、dbに関する操作を行えるようになります。
モデル
db マクロの中でオブジェクトを定義することで、DBのスキーマ定義になります。
type名やフィールドに * アスタリスクがついてますが、これはpublicであることを示しています。
withDb:
このブロックのボディ内で、dbに関する操作を実行することができます。
createTables(bool)もnormで定義されているテンプレート関数で、
実行するとdbマクロ内で定義されたオブジェクト名で、DBのテーブルを作成します。
引数のforceをtrueにすると、すでに同じ名前のテーブルがあっても上書きして作成をします。
2. オブジェクトをDBに記録する
import strformat
import logging
# コンソールロガーを登録
addHandler newConsoleLogger()
# insert
withDb:
var test: Post
for i in 1..10: # 1から10まで繰り返す。(9までではない)
test = Post(
title: fmt"{i}th title",
author: "max",
content: fmt"{i}th content",
)
test.insert()
insert()
post.insert()はテンプレートメソッドです。
db マクロ内部で定義したオブジェクトに対して実装されます。
実行すると、オブジェクトをDBに記録します。
logging
normのDB操作は、addHandler newConsoleLogger()
を実行しておくとコンソールに出力できます。
addHandlerはproc(関数みたいなやつ)なのですが、Rubyのように()がなくても引数を指定して実行できます。
PythonっぽいのにRubyの文法も取り込んでるのがなんか面白い。
フォーマット文字列 fmt
strformat パッケージをインポートすると、フォーマット文字列 fmt"{}" が使えるようになります。
この文字列の中で、ブラケット{} で変数を囲むと値に置き換わります。
かなり便利。
書き込めたか確認してみる
dockerコンテナのPostgreSQLにログインしてSQL文を打ち込んでみます。
docker exec -it nim_rest_db_1 psql -U nim -d nim
# postgresコンテナ内のpsqlコンソール
nim=# select * from post;
# 出力結果
id | title | author | content
----+------------+--------+--------------
1 | 1th title | max | 1th content
2 | 2th title | max | 2th content
3 | 3th title | max | 3th content
4 | 4th title | max | 4th content
5 | 5th title | max | 5th content
6 | 6th title | max | 6th content
7 | 7th title | max | 7th content
8 | 8th title | max | 8th content
9 | 9th title | max | 9th content
10 | 10th title | max | 10th content
問題なく、DBにレコードを作成できました。
3. DBからオブジェクトから取り出す
import sugar
# select
withDb:
# 1つだけ取る
var
id = 1
result = Post.getOne(id)
dump result
# result = (id: 1, title: "1th title", author: "max", content: "1th content")
# いっぱい取る
var
limit = 10
offset = 0
many_posts = Post.getMany(limit, offset)
dump many_posts
# many_posts = @[(id: 1, title: "1th title", author: "max", content: "1th content"), (id: 2 ...
# 条件をつけて取る
var
min_id = 4
max_id = 8
cond_posts = Post.getMany(limit, offset,
"id >= ? AND id <= ? ORDER BY id DESC", # WHERE文に相当する。
[$min_id, $max_id] # WHERE文の?マークに埋め込める文字列。string型にしないとだめなので、 $演算子で文字列に変換
)
dump cond_posts
# cond_posts = @[(id: 8, title: "8th title", author: "max", content: "8th content"), (id: 7, ...
getOne() と getMany()
どちらもテンプレートメソッドで、insert()と同じくdb マクロ内部で定義したオブジェクトに実装されます。
クラスメソッドっぽい感じなので、インスタンスではなく type.method() みたいに書きます。
それぞれ、戻り値の型が
- Post.getOne() : Post
- Post.getMany() : seq[Post]
となっており、getMany()ではsequece[object]型で複数の値が獲得できます。
dump()
テンプレート関数。
sugar パッケージからimportされたものです。
リファレンスによれば、式の内容をダンプして出力する関数で、デバッグの際にオブジェクトなどを出力するのに役立ちます。
https://nim-lang.org/docs/sugar.html
sugar モジュールはその名の通り、シンタックスシュガーを集めたパッケージで他にも以下のようなものがあります。
- es6以降のアロー関数演算子 =>
- Rust風の関数識別子 ->
条件をつけてレコードを取得
getOne() と getMany() は、SQLにおけるWHERE文に相当する部分を、
文字列の引数として渡すことができます。
上のコードでは、idが 4~8 のデータを降順で取得しています。
4. データを更新する
# update
withDb:
var
id = 1
before = Post.getOne(id)
before.content = "I have update this content!"
before.update()
# 変更できているか確認
var after = Post.getOne(id)
dump after
# new_post = (id: 1, title: "1th title", author: "max", content: "I have update this content!")
もう簡単ですね
- DBからオブジェクトを取得
- オブジェクトの内容を変更する
- obj.update() を実行
これだけです。
5. データを削除する
# delete
withDb:
var
id = 1
post = Post.getOne(id)
post.delete()
# 削除したレコードを取得しようとするとエラー
try:
var deleted = Post.getOne(id)
dump deleted
except KeyError:
let e = getCurrentException()
echo repr(e)
これも簡単です。
オブジェクトを取得してきて obj.delete() を実行するだけです。
すでに削除したレコードをid指定で取ってこようとするとKeyErrorが発生します。
try ~ except 文を使うと確認できます。
まとめ
今回やったのは、
- nimbleプロジェクトの作成
- ORMパッケージ norm
- DB接続
- Postモデル定義
- CRUD操作
です。
normはここで説明した以外にも、
- 文字列フィールドのイニシャルを大文字にする。
- DBレコードにNULL値を許容する。
など、さまざまな設定があるので、詳細な使い方をしたい人は調べてみてください。
次回、webサーバー編
祝 Nim v1.0.0 リリース!NimでAPIサーバーを書いてみる。 サーバー編
https://qiita.com/harumaxy/items/f37a79dc5b959a97a017
続く
Author And Source
この問題について(祝 Nim v1.0.0 リリース!NimでAPIサーバーを書いてみる。 ORM編), 我々は、より多くの情報をここで見つけました https://qiita.com/harumaxy/items/293bbe0d4cf12ecaa420著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .