БэкендTypeScriptJavaScript

Bun + TypeScript: делаем первый REST API без Express

Создаём REST API на Bun и TypeScript без Express: маршруты GET и POST, проверка JSON, ответы со статусами и тестирование через curl.

Кодик

Автор

6 мин чтения

Bun REST API можно освоить на одном небольшом проекте, если сразу видеть вход, действие и результат. Встроенный Bun.serve принимает HTTP-запросы и возвращает стандартные Response, поэтому первый API помещается в один TypeScript-файл. Ниже вы соберёте работающий пример и проверите обычный и граничный сценарии.

Express часто добавляют по привычке, хотя для учебных GET и POST достаточно возможностей самого runtime. Поэтому отделим настройку, входные данные, основное действие и проверку. В этой статье вы соберёте API заметок с GET /notes, POST /notes, валидацией JSON и корректными статусами. Проверка: GET возвращает массив, валидный POST создаёт заметку со статусом 201, а пустой title получает 400.

1Соберите минимум

установить Bun и создать server.ts. Не расширяйте проект до первого успешного запуска.

2Увидьте данные

curl получает статус 200 и массив. Проверяйте значение, а не отсутствие ошибки.

3Измените правило

После успеха добавьте поле done и PATCH-маршрут, который меняет только существующую заметку. Так копирование станет практикой.

Как устроен учебный проект Bun REST API

HTTP-сервер передаёт Request в функцию fetch, роутер выбирает действие по методу и URL, а каждая ветка возвращает Response. Держите ввод, логику и отображение раздельно: тогда каждый этап можно проверить отдельно.

ЭтапДействиеПроверяемый результат
Средаустановить Bun и создать server.tsbun --version и bun run server.ts работают
Чтениеобработать GET /notes и вернуть JSONcurl получает статус 200 и массив
Созданиепрочитать JSON в POST и добавить записьответ содержит новую заметку и статус 201
Ошибкаотклонить пустой title и неизвестный путьклиент получает 400 или 404 с понятным JSON

Контрольные точки: среда, чтение, создание и ошибка. Следующий шаг начинается после совпадения с ожидаемым результатом.

Путь данных в проекте Bun REST API
От входных данных до видимого результата. Сначала проверяется вход, затем действие и только потом интерфейс.

Зафиксируйте первый успех Запустите сервер и откройте http://localhost:3000/notes. До добавления POST должен вернуться пустой JSON-массив. Сохраните команду запуска и ожидаемый вывод.

Собираем рабочий пример шаг за шагом

Пример сверялся с актуальной документацией официальной документации Bun.serve на 19 октября 2026 года. Установите Bun по официальной инструкции, затем создайте папку и выполните bun init -y. Проверьте текущую папку через pwd или Get-Location. Вставьте пример целиком, выполните один раз, затем разбирайте по частям.

type Note = { id: number; title: string }
const notes: Note[] = []

Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url)

    if (url.pathname === "/notes" && request.method === "GET") {
      return Response.json(notes)
    }

    if (url.pathname === "/notes" && request.method === "POST") {
      const body = await request.json() as { title?: string }
      const title = body.title?.trim()
      if (!title) return Response.json({ error: "title required" }, { status: 400 })

      const note = { id: notes.length + 1, title }
      notes.push(note)
      return Response.json(note, { status: 201 })
    }

    return Response.json({ error: "not found" }, { status: 404 })
  },
})

console.log("http://localhost:3000")
Что должно произойти после запуска
  • GET /notes отвечает [], а после создания содержит новую заметку.
  • POST с {"title":"Изучить Bun"} возвращает 201 и объект с числовым id.
  • Повторный запуск даёт тот же предсказуемый результат.
  • Пустой ввод получает понятное сообщение вместо падения.

Bun использует знакомые веб-объекты Request, Response и URL. Поэтому основная логика не зависит от Express API. Найдите в примере вход, проверку и место формирования результата.

Хрупкий и проверяемый вариант Bun REST API
Что отличает устойчивый проект от случайного запуска. Явные границы делают ошибку локальной и понятной.

Что происходит внутри и почему это работает

Обработчик fetch вызывается для каждого запроса. URL отделяет путь от полного адреса, метод различает чтение и создание, а Response задаёт тело, заголовки и статус. Логику держите отдельно от вывода: функция получает аргумент и возвращает значение, а интерфейс показывает его. Массив хранится только в памяти процесса. После перезапуска данные исчезнут, и это нормальное ограничение учебного примера, а не скрытая база.

ЧастьЗа что отвечаетЧто проверить
Маршрутизациясравнивает pathname и methodнеизвестный путь получает ровно 404
Валидациячитает JSON и проверяет непустой titleпустая строка и отсутствующее поле получают 400
Ответформирует JSON и правильный HTTP-статусклиент различает чтение, создание и ошибку

Не ловите все ошибки одним пустым catch. Невалидный JSON отличается от пустого title и ошибки сервера. В следующей версии оберните чтение тела, верните 400 для синтаксически плохого JSON и оставьте 500 только для неожиданной ситуации. Не прячьте поток в одном обработчике: сохраните вход, проверьте его, вызовите операцию и только затем обновите интерфейс.

Проверьте границу Отправьте curl -i -X POST localhost:3000/notes -H "content-type: application/json" -d "{} и проверьте статус 400. Меняйте одно условие за прогон, чтобы видеть причину.

Проверяем проект и добавляем свою функцию

Добавьте DELETE /notes/:id. Сначала разберите id из пути, затем проверьте существование заметки и только после этого удаляйте. До правки запишите ожидаемое поведение одним предложением. После граничного теста повторите обычный сценарий.

Проверка своими руками
  1. Сохраните ожидаемый результат: GET возвращает массив, валидный POST создаёт заметку со статусом 201, а пустой title получает 400.
  2. Измените правило: добавьте поле done и PATCH-маршрут, который меняет только существующую заметку. Сначала предскажите итог.
  3. Передайте граничное значение и убедитесь, что приложение не падает.
  4. Верните корректные данные и объясните роли трёх основных частей.
Готово, если выполняются все пункты
  • bun --version и bun run server.ts работают
  • curl получает статус 200 и массив
  • ответ содержит новую заметку и статус 201
  • клиент получает 400 или 404 с понятным JSON

Фреймворк стоит добавлять, когда его маршрутизация, плагины или соглашения решают реальную проблему, а не до первого запроса. Полезная правка проходит от нового ввода до видимого результата.

Четыре контрольные точки Bun REST API
Четыре проверки перед следующим шагом. Каждый следующий этап начинается после наблюдаемого результата.

Что запомнить и куда двигаться дальше

После учебного массива подключите постоянное хранилище, схему валидации и тесты. Контракт HTTP уже можно оставить тем же. Для закрепления откройте в REST API на Express, в нативном запуске TypeScript и в проверке JSON через Zod.

Как изучить тему в Кодике

Закрепите объекты, массивы, async/await и HTTP-статусы до подключения базы. Сначала восстановите главную функцию и проверьте её на трёх значениях.

ШагЧто разобратьМини-проверка
1. ОсноваRequest, Response, URL и JSON в JavaScriptОбъясните вход и выход функции.
2. ИнструментBun.serve, методы HTTP и запуск TypeScriptПовторите команду по памяти.
3. ПроектТри маршрута с проверкой входаПроверьте два сценария.
4. РефакторингРазделение router, service и storageРазделите ввод, логику и вывод.

Попробуйте вызвать API не только curl, но и fetch из маленькой HTML-страницы. Базовый синтаксис закрепите через курс JavaScript.

Всегда статус 200

Клиент не отличает создание от ошибки. Используйте 201, 400, 404 и 500 по смыслу.

Доверие request.json

Тело может быть невалидным JSON. Обрабатывайте ошибку чтения отдельно.

Данные считаются постоянными

Массив очищается при перезапуске. Подпишите это ограничение в README.

Один длинный fetch

С ростом маршрутов вынесите разбор пути и функции действий, иначе ветки станут нечитаемыми.

Сверяйтесь с первичной документацией. Команды и ограничения примера проверены по официальной документации Bun.serve. Если версия инструмента изменилась, официальный источник важнее старого скриншота.

Что получится в итоге

Готовый результат проекта: Bun + TypeScript: делаем первый REST API без Express
Так выглядит законченный учебный проект: есть понятный вход, видимый результат, защита от неверных данных и место для следующего улучшения.

Короткие ответы
Нужен ли Express для Bun.serve?

Нет. Runtime уже предоставляет HTTP-сервер и стандартные веб-объекты.

Почему POST возвращает 201?

Запрос успешно создал новый ресурс, и этот статус точнее обычного 200.

Что проверять первым, если Bun REST API не запускается?

Версию среды, текущую папку, команду запуска и первую строку ошибки.

Как понять, что проект Bun REST API работает?

Проверьте основной сценарий, неверный ввод, повторный запуск и один граничный случай.

Можно ли начать с готового кода?

Да. Добейтесь результата, измените одно правило и восстановите ключевую функцию без копирования.

Сделайте один осознанный шаг дальше

Выберите одно улучшение и проведите его от ввода до результата.

Если не хватает основы, перейдите курс JavaScript, затем восстановите ключевую функцию без подсказки.