Разработка простого приложения для заметок на HappyX

Как создать веб-приложение, если вы пишите на 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

Пробуем сделать запрос:

Результат POST запроса на создание заметки

Результат POST запроса на создание заметки

Взглянем на нашу таблицу:

Содержимое таблицы notes

Содержимое таблицы notes

Отлично! Что дальше?

Теперь попробуем сделать 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:

Результат выполнения GET запроса на получение всех заметок

Результат выполнения GET запроса на получение всех заметок

Наконец, создадим 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:

Успешный результат выполнения PATCH запроса

Успешный результат выполнения PATCH запроса

Ошибочный результат выполнения PATCH запроса

Ошибочный результат выполнения PATCH запроса

Круто! Что теперь?

На этом разработка серверной части окончена. Теперь мы перейдем к написанию клиентской части.

Клиентская часть

Здесь мы переходим в 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..

Давайте получим список заметок при загрузке страницы:

# Сразу получаем список заметок
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 addNote(newName).then(() => (discard updateNotes()))
            newName.set("")
      tDiv(class = "flex flex-col gap-2"):
        # Пробегаемся по заметкам
        for i in 0.. (discard updateNotes()))

Наконец-то, теперь мы можем запустить?

Все верно! Самое время протестировать наше приложение! Запускаем бэкенд:

nim c -r server/src/main

И отдельно запускаем фронтенд:

cd client
hpx dev --reload --port 8000

Итоговое приложение с запущенным бэкендом и фронтендом

Итоговое приложение с запущенным бэкендом и фронтендом

Вот и все! Мы написали простое веб-приложение, используя Nim и веб-фреймворк HappyX.

Полезные ссылки

© Habrahabr.ru