Developing a simple note taking app on HappyX
How to create a web application if you write in Nim? What is HappyX and how can you create a note-taking application with it? You will learn all about it in the full article.
Hello! My name is Nikita, and today I want to tell you about creating web applications in the Nim programming language.
Briefly about Nim
Let's start with something simple. Nim – is a programming language that can be compiled into C, C++, Objective C and JavaScript. Thanks to this, we can write both server and client applications.
If you don't have Nim installed, install it by following these steps: instructions on the official site.
What is HappyX?
HappyX is a full-featured web framework written in Nim itself. And based on the above, it allows you to develop server and client web applications. For the server side it is compiled in C, and for the client side in JavaScript.
Okay, so what next?
Server part
We will develop it on the same HappyX using the primitive sqlite presented to us library Nim.
First you need to install the libraries happyx And db_connector. This is done using a package manager. nimble:
nimble install happyx@#head
nimble install db_connector
After installing the libraries, create a project using happyx cli:
hpx create --name server --kind SSR --language Nim
Next, while executing the command, select don't use templates and create a client project:
hpx create --name client --kind SPA --language Nim -u
Now let's go to /server/src/main.nim
and import the necessary libraries:
import
happyx,
db_connector/db_sqlite
Next, let's create query models for creation And editing our notes:
# Модель запроса
# Ниже мы будем обрабатывать ее в виде JSON
model CreateNote:
# единственное обязательное поле string
name: string
model EditNote:
completed: bool
Next we declare our server application:
# Задаем хост и порт
serve "127.0.0.1", 5000:
# Преднастройка сервера в gcsafe (garbage collector safe) области
setup:
# Подключаем базу данных
var db = open("notes.db", "", "", "")
# Создаем таблицу, если она не существует
db.exec(sql"""
CREATE TABLE IF NOT EXISTS notes(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(50) NOT NULL,
completed INTEGER NOT NULL DEFAULT 0
);
""")
Nothing complicated yet, right?
Let's create POST
request to create a note:
serve "127.0.0.1", 5000:
setup:
...
# Объявляем POST запрос для создания новой заметки
post "/note[note:CreateNote]":
# Выведем название заметки
echo note.name
# Вставляем заметку в базу данных и получаем ее ID
let id = db.insertId(sql"INSERT INTO notes (name) VALUES (?)", note.name)
# Возвращаем ID заметки в ответе
return {"response": id}
And we launch our application:
nim c -r server/src/main
Let's try to make a request:
Let's take a look at our table:
Great! What's next?
Now let's try to do GET
request to get all notes:
post "/note[note:Note]":
...
# GET запрос для получения всех заметок
get "/notes":
# Список заметок:
var notes = %*[]
# Пробегаемся по всем строчкам:
for row in db.rows(sql"SELECT * FROM notes"):
# Добавляем новый элемент в список
notes.add %*{"id": row[0].parseInt, "name": row[1], "completed": row[2] != "0"}
return {"response": {
"items": notes,
"size": notes.len
}}
Let's take a look at Postman:
Finally, let's create PATCH
request:
get "/notes":
...
# PATCH запрос на изменение заметки по ее ID
patch "/note/{noteId:int}[note:EditNote]":
# Смотрим, есть ли такая заметка вообще
var row = db.getRow(sql"SELECT * FROM notes WHERE id = ?", noteId)
# заметка не найдена - возвращаем ошибку
if row[0] == "":
statusCode = 404
return {"error": "заметка с таким ID не найдена"}
# Обновляем нашу заметку
db.exec(sql"UPDATE notes SET completed = ? WHERE id = ?", note.completed, noteId)
# И возвращаем успешный статус
return {"response": "success"}
And let's look at Postman again:
Cool! Now what?
This completes the development of the server part. Now we will move on to writing the client part.
Client side
Here we go to client/src/main.nim
and edit it. First, let's look at importing:
import
happyx,
std/strformat, # форматирование строк
std/jsfetch, # fetch
std/asyncjs, # async
std/sugar, # синтаксический сахар
std/httpcore, # HTTP методы
std/json # работа с JSON
Now let's set a basic link to our API:
# базовый URL для API
const BASE = "http://localhost:5000"
And we will declare the note type for further processing:
# тип для заметки
type Note = object
id: cint
name: cstring
completed: bool
Then we declare reactive variables:
var
# реактивный список заметок
notes = remember newSeq[Note]()
# реактивное название для новой заметки
newName = remember ""
Now let's go back to the server part and remember what we wrote there. Let's create three procedures for interacting with the API:
proc updateNotes() {.async.} =
# Делаем запрос к серверу на получение всех заметок
await fetch(fmt"{BASE}/notes".cstring)
# Получаем JSON
.then((response: Response) => response.json())
.then(proc(data: JsObject) =
# Преобразуем JSON в список Note
var tmpNotes: seq[Note] = collect:
for i in data["response"]["items"]:
i.to(Note)
# Если размер списка не изменился - просто меняем параметры
if notes.len == tmpNotes.len:
for i in 0..<tmpNotes.len:
notes[i] = tmpNotes[i]
else:
# Если размер списка изменился - полностью меняем список
notes.set(tmpNotes)
)
proc toggleNote(note: Note) {.async.} =
# Отправляем PATCH запрос
discard await fetch(fmt"{BASE}/note/{note.id}".cstring, newfetchOptions(
HttpPatch, $(%*{"completed": not note.completed})
))
proc addNote(name: string) {.async.} =
# Отправляем POST запрос
discard await fetch(fmt"{BASE}/note".cstring, newfetchOptions(
HttpPost, $(%*{"name": name})
))
Let's get a list of notes when the page loads:
# Сразу получаем список заметок
discard updateNotes()
Now it's time for layout!
# Объявляем наше одностраничное приложение в элементе с ID app
appRoutes "app":
# Главный маршрут
"/":
tDiv(class = "flex flex-col gap-2 w-fit p-8"):
tDiv(class = "flex"):
# input для
tInput(id = "newNameChanger", class = "rounded-l-full px-6 py-2 border-2 outline-none", value = $newName):
@input:
# Меняем название заметки
newName.set($ev.target.InputElement.value)
tButton(class = "bg-green-400 hover:bg-green-500 active:bg-green-600 rounded-r-full px-4 py-2 transition-all duration-300"):
"Добавить"
@click:
# Добавляем новую заметку
discard addNote(newName).then(() => (discard updateNotes()))
newName.set("")
tDiv(class = "flex flex-col gap-2"):
# Пробегаемся по заметкам
for i in 0..<notes.len:
tDiv(
class =
# Меняем класс в зависимости от выполненности заметки
if notes[i].completed:
"rounded-full select-none px-6 py-2 cursor-pointer hover:scale-110 translation-all duration-300 bg-green-300"
else:
"rounded-full select-none px-6 py-2 cursor-pointer hover:scale-110 translation-all duration-300 bg-red-300"
):
# аналогично с эмоджи
if notes[i].completed:
"✅ "
else:
"❌ "
{notes[i].name}
@click:
# При нажатии шлем PATCH запрос и обновляем список заметок
discard toggleNote(notes[i]).then(() => (discard updateNotes()))
Finally, can we launch now?
That's right! It's time to test our application! Let's launch the backend:
nim c -r server/src/main
And we launch the frontend separately:
cd client
hpx dev --reload --port 8000
That's it! We've written a simple web application using Nim and the HappyX web framework.