As a language agnostic person who knows multiple human languages (Azerbaijani, Turkish, Ukrainian, Russian, English, German, a bit of Portuguese) and programming languages (PHP, JS, Ruby, Python, Go, Dart, Elixir) this time I am picking Elixir as language/stack which makes my brain and soul meditate during coding.
I truly enjoy Ruby kind of language features, but(!) I like Elixir way of writing functional code, where you can overload your methods for different kinds of data and shape and based on that can build crazy function piping combinations.
Also immutability of inputs at end of function execution tells me that I should not worry about side effects cause there is no shared state.
As web framework gold standard Phoenix framework.
It’s Ruby on Rails of Elixir, it’s mature, it’s well documented, it has great community.
As database I’m going to use CouchDB.
Reasons for choosing CouchDB this posts can tell everything:
Why CouchDB?
IBM - What is CouchDB?
My opinion about CouchDB is - it provides simple GET/SET methods to work with, provides easy to use administration methods about replication, keeps data change history (revisions on documents), provides attachments feature where You can have kinda User
document and profileImage
as attachment which adds extra W in comparison with S3.
It gives me to at DB as place where I store/retrieve data, manipulating and doing business logic on Application level.
So I don’t need to do complex sql queries, I can prepare the data which needed - I can simply subscribe to change feed and calculate data as extra app process and update related document(s), it gives me ability to not overload DB with extra responsibility which needed during joins, groupings and etc.
Also I’ll look like idiot and crazy but I want challenges! (:
I want some unique solution and write about it my blog.
As a console gamer I always try to pick Hard difficulty modes to enjoy the process.
It’s like going custom mode of difficulty and dragging enemy AI to maximum, enemy life maximum, own life to minimum.
Come on! It will be fun!
So here goes the process!
1. DataBase
Picked OS version and downloaded CouchDB from here
Since I’m using MacOS it will be installed as drag-dropping application to Applications folder.
Run the app and login to Admin panel.
Create database:
Create user (based on manual let’s create a document in _users database):
{
"_id": "org.couchdb.user:codomari",
"name": "codomari",
"password": "password to be hashed",
"roles": [],
"type": "user"
}
and it hashes our password automatically:
to verify created db user we can send curl request:
┌[num8er☮g8way1.local]-(~)
└> curl -v -X POST http://localhost:5984/_session -d 'name=codomari&password=codomari'
Note: Unnecessary use of -X or --request, POST is already inferred.
* Host localhost:5984 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:5984...
* connect to ::1 port 5984 from ::1 port 50435 failed: Connection refused
* Trying 127.0.0.1:5984...
* Connected to localhost (127.0.0.1) port 5984
> POST /_session HTTP/1.1
> Host: localhost:5984
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Length: 31
> Content-Type: application/x-www-form-urlencoded
>
* upload completely sent off: 31 bytes
< HTTP/1.1 200 OK
< Cache-Control: must-revalidate
< Content-Length: 41
< Content-Type: application/json
< Date: Thu, 14 Nov 2024 13:46:25 GMT
< Server: CouchDB/3.4.2 (Erlang OTP/25)
< Set-Cookie: AuthSession=Y29kb21hcmk6NjczNUZGMzI63G6Lh6vQ5aVy-YbiXDB6FjMqu3iC_qpBDnb1SVp6778; Version=1; Expires=Thu, 14-Nov-2024 13:56:26 GMT; Max-Age=600; Path=/; HttpOnly
<
{"ok":true,"name":"codomari","roles":[]}
keep in mind with empty roles
field can keep any string values except _admin
, to create admin You’ve to configure system files by manual.
This means our codomari
user cannot access database admin ui, you’ll see empty interface like this:
Add db admin and member users:
Based on official manual here about creating database and first document we have to put our data as document to same same database.
This means our data must have “type” field to distinguish type of entity.
Example:
{
"_id": "user:3c3359bb-27e4-464a-bb4e-045673da769b",
"type": "User",
"credentials": {"username": "num8er", "password": "hashed-password-here"},
"personal": {
"dateOfBirth": {"year": 1988, "month": 1, "day": 8},
"name": ["Anar", "K", "Jafarov"]
}
}
as a result we will get document with revision field:
which changes when we change data in document:
Let’s check if our codomari
user can access db:
┌[num8er☮g8way1.local]-(~)
└> curl -u codomari:codomari http://localhost:5984/codomari/_all_docs
{"total_rows":1,"offset":0,"rows":[
{"id":"user:3c3359bb-27e4-464a-bb4e-045673da769b","key":"user:3c3359bb-27e4-464a-bb4e-045673da769b","value":{"rev":"4-076f6d48ae9cae9a2ea018eca46538f8"}}
]}
For now temporarily we are done with db.
Need to solve connectivity and structure our code.
2. Bootstrapping our API
As our app will be done using Elixir language stack after installing it, we need to install Phoenix framework before creating Phoenix app:
Step 1: install Hex
mix local.hex
┌[num8er☮g8way1.local]-(~/My/Codomari)-
└> mix local.hex
* creating /Users/num8er/.mix/archives/hex-2.1.1
Step 2: install Phoenix application generator
mix archive.install hex phx_new
[num8er☮g8way1.local]-(~/My/Codomari)-
└> mix archive.install hex phx_new
Resolving Hex dependencies...
Resolution completed in 0.006s
New:
phx_new 1.7.14
* Getting phx_new (Hex package)
All dependencies are up to date
Compiling 11 files (.ex)
Generated phx_new app
Generated archive "phx_new-1.7.14.ez" with MIX_ENV=prod
* creating /Users/num8er/.mix/archives/phx_new-1.7.14
Step 3: create Phoenix app
Let’s create Phoenix app with minimum stuff.
So based on docs:
mix phx.new codomari_backend --no-ecto --no-assets --no-live
┌[num8er☮g8way1.local]-(~/My/Codomari)
└> mix phx.new codomari_backend --no-ecto --no-assets --no-live
* creating codomari_backend/lib/codomari_backend/application.ex
* creating codomari_backend/lib/codomari_backend.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/error_json.ex
* creating codomari_backend/lib/codomari_backend_web/endpoint.ex
* creating codomari_backend/lib/codomari_backend_web/router.ex
* creating codomari_backend/lib/codomari_backend_web/telemetry.ex
* creating codomari_backend/lib/codomari_backend_web.ex
* creating codomari_backend/mix.exs
* creating codomari_backend/README.md
* creating codomari_backend/.formatter.exs
* creating codomari_backend/.gitignore
* creating codomari_backend/test/support/conn_case.ex
* creating codomari_backend/test/test_helper.exs
* creating codomari_backend/test/codomari_backend_web/controllers/error_json_test.exs
* creating codomari_backend/lib/codomari_backend_web/controllers/error_html.ex
* creating codomari_backend/test/codomari_backend_web/controllers/error_html_test.exs
* creating codomari_backend/lib/codomari_backend_web/components/core_components.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_controller.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_html.ex
* creating codomari_backend/lib/codomari_backend_web/controllers/page_html/home.html.heex
* creating codomari_backend/test/codomari_backend_web/controllers/page_controller_test.exs
* creating codomari_backend/lib/codomari_backend_web/components/layouts/root.html.heex
* creating codomari_backend/lib/codomari_backend_web/components/layouts/app.html.heex
* creating codomari_backend/lib/codomari_backend_web/components/layouts.ex
* creating codomari_backend/priv/static/images/logo.svg
* creating codomari_backend/lib/codomari_backend/mailer.ex
* creating codomari_backend/lib/codomari_backend_web/gettext.ex
* creating codomari_backend/priv/gettext/en/LC_MESSAGES/errors.po
* creating codomari_backend/priv/gettext/errors.pot
* creating codomari_backend/priv/static/robots.txt
* creating codomari_backend/priv/static/favicon.ico
* creating codomari_backend/priv/static/assets/app.js
* creating codomari_backend/priv/static/assets/app.css
* creating codomari_backend/priv/static/assets/home.css
Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile
We are almost there! The following steps are missing:
$ cd codomari_backend
Start your Phoenix app with:
$ mix phx.server
You can also run your app inside IEx (Interactive Elixir) as:
$ iex -S mix phx.server
--no-ecto
- Ecto
does have providers for CouchDB
, but I’m planning to work with db directly using couchbeam and calling it’s methods through DAO-s which return Entities. Couchbeam actively supported (yes, I did checked different couchdb libraries for Elixir, but all of them not updated during last 2 years) is reason why I picked it.
--no-live
- we are going to make http api and not providing liveview stuff which renders veiws and data change in “realtime”.
--no-assets
- same reason as with --no-live
flag.
Step 4: check created app
cd codomari_backend
mix phx.server
┌[num8er☮g8way1.local]-(~/My/Codomari)
└> cd codomari_backend
┌[num8er☮g8way1.local]-(~/My/Codomari/codomari_backend)-[git://main ✔]-
└> mix phx.server
Compiling 14 files (.ex)
warning: defining a Gettext backend by calling
use Gettext, otp_app: ...
is deprecated. To define a backend, call:
use Gettext.Backend, otp_app: :my_app
Then, instead of importing your backend, call this in your module:
use Gettext, backend: MyApp.Gettext
lib/codomari_backend_web/gettext.ex:23: CodomariBackendWeb.Gettext (module)
Generated codomari_backend app
[info] Running CodomariBackendWeb.Endpoint with cowboy 2.12.0 at 127.0.0.1:4000 (http)
[info] Access CodomariBackendWeb.Endpoint at http://localhost:4000
as result in browser we will see such screen:
For now minimum necessary stuff is done.
In next iteration going to integrate CouchDB client and do few endpoints to check.
Talk Soon!