Как создать веб-приложение, если вы пишите на Nim? Что такое HappyX и как можно создать на нем приложение для заметок? Обо всем этом вы узнаете в полной статье.
Здравствуйте! Меня зовут Никита, и сегодня я хочу рассказать вам о создании веб-приложений на языке программирования Nim.
Коротко о Nim
Начнем с простого. Nim - это язык программирования, который может компилироваться в C, C++, Objective C и в JavaScript. Благодаря этому мы можем писать как серверные приложения, так и клиентские.
Если у вас не установлен Nim, установите его, следуя инструкции на официальном сайте.
Что такое HappyX?
HappyX - это полнофункциональный веб-фреймворк, написанный на этом самом Nim. И исходя из вышесказанного, он позволяет разрабатывать серверные и клиентские веб-приложения. Для серверной части он компилируется в C, а для клиентской в JavaScript.
Хорошо, а дальше что?
Серверная часть
Разрабатывать мы ее будем все на том же HappyX с использованием примитивной sqlite, представленной нам библиотекой Nim.
Сперва необходимо поставить библиотеки happyx и db_connector. Делается это с помощью пакетного менеджера nimble:
nimble install happyx@#head
nimble install db_connector
После установки библиотек создаем проект с помощью happyx cli:
hpx create --name server --kind SSR --language Nim
Далее во время выполнения команды выбираем don't use templates и создаем клиентский проект:
hpx create --name client --kind SPA --language Nim -u
Теперь переходим в /server/src/main.nim
и импортируем необходимые библиотеки:
import
happyx,
db_connector/db_sqlite
Затем создадим модели запроса для создания и редактирования наших заметок:
# Модель запроса
# Ниже мы будем обрабатывать ее в виде JSON
model CreateNote:
# единственное обязательное поле string
name: string
model EditNote:
completed: bool
Далее объявляем наше серверное приложение:
# Задаем хост и порт
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
);
""")
Пока что ничего сложного, верно?
Создадим POST
запрос на создание заметки:
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}
И запускаем наше приложение:
nim c -r server/src/main
Пробуем сделать запрос:
Взглянем на нашу таблицу:
Отлично! Что дальше?
Теперь попробуем сделать GET
запрос на получение всех заметок:
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
}}
Взглянем на Postman:
Наконец, создадим PATCH
запрос:
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"}
И снова смотрим Postman:
Круто! Что теперь?
На этом разработка серверной части окончена. Теперь мы перейдем к написанию клиентской части.
Клиентская часть
Здесь мы переходим в client/src/main.nim
и редактируем его. Для начала разберемся с импортом:
import
happyx,
std/strformat, # форматирование строк
std/jsfetch, # fetch
std/asyncjs, # async
std/sugar, # синтаксический сахар
std/httpcore, # HTTP методы
std/json # работа с JSON
Теперь зададим базовую ссылку на наше API:
# базовый URL для API
const BASE = "http://localhost:5000"
И объявим тип заметки для дальнейшей обработки:
# тип для заметки
type Note = object
id: cint
name: cstring
completed: bool
После чего объявим реактивные переменные:
var
# реактивный список заметок
notes = remember newSeq[Note]()
# реактивное название для новой заметки
newName = remember ""
Теперь вернемся к серверной части и вспомним, что мы там писали. Создадим три процедуры для взаимодействия с 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})
))
Давайте получим список заметок при загрузке страницы:
# Сразу получаем список заметок
discard updateNotes()
А теперь время верстки!
# Объявляем наше одностраничное приложение в элементе с 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 await addNote(newName)
discard await 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 await toggleNote(notes[i])
discard await updateNotes()
Наконец-то, теперь мы можем запустить?
Все верно! Самое время протестировать наше приложение! Запускаем бэкенд:
nim c -r server/src/main
И отдельно запускаем фронтенд:
cd client
hpx dev --reload --port 8000
Вот и все! Мы написали простое веб-приложение, используя Nim и веб-фреймворк HappyX.
Полезные ссылки
Исходный код из данной статьи
Документация по Nim
Гайд по HappyX
Комментарии (5)
Sollita
29.06.2024 10:14+1Ничего, что первая картинка не соответствует Вашему коду?
akihayase Автор
29.06.2024 10:14Ничего страшного в этом нет, это же всего лишь обложка статьи
Sollita
29.06.2024 10:14А, по-моему, это введение читателей в заблуждение. Если бы не эта картинка, я бы вообще не стала читать эту статью.
Стек простым не бывает, поэтому меня и заинтересовало, как Вам это удалось. Но оказалось, что ничего про стек в Вашей статье нету, а есть реклама бог знает чего.
Я знаю, как сделать то, что на первой картинке нарисовано, а Вы?
yarkov
Жесть какая ))