Пробуем TypeScript на сервере

Доброго времени суток.

В этой статье мы познакомимся с языком программирования TypeScript на примере создания небольшого серверного приложения (простая авторизация), используя такие популярные технологии как Node.js и Express.js. Также вкратце коснёмся рассмотрения довольно интересной embeddeb NoSQL базы данных NeDB.

Статья ориентирована прежде всего на людей уже знакомых с Node и Express и не рассматривает самые базовые вещи, относящиеся к этим технологиям. Я не буду подробно описывать и все особенности TypeScript, а код далеко не является эталонным — это всего лишь пример. Однако, если у вас есть желание попробовать TypeScript, но вы не знаете как к нему подобраться, особенно со стороны backend’a,  тогда, возможно, вы найдёте что-то полезное в этой статье. Ссылка на GitHub репозиторий в конце статьи.

TypeScript

TypeScript (далее по тексту TS) — это язык программирования, являющийся надмножеством JavaScript. Начал разрабатываться компанией Microsoft и был впервые представлен в 2012 году. Автор языка — Андреас Хейлсберг, который до TS приложил руку к разработке таких языков как C# и Delphi. В самом Microsoft заявляют о TS  следующим образом: «Язык программирования TypeScript является средством разработки веб-приложений, расширяющим возможности JavaScript».

Технология сама по себе довольно интересная и выгодно отличается от того же JavaScript. Главное, что выделяет TS — это строгая типизация, а также более привычная многим (java-like) реализация ООП. В TS вы можете использовать всё, что есть на данный момент в JavaScript, плюс объявления типов, модификаторы доступа, классы, интерфейсы и ещё много всего. Получается такой JavaScript с человеческим лицом. При этом компилируется TS в тот-же JavaScript. На выбор любой из стандартов: ES3, ES5 или ES6. Как и большинство новых технологий TS является open source проектом, доступным в репозитории на GitHub: https://github.com/Microsoft/TypeScript Ещё одним аргументом в пользу данного языка является тот факт, что на нём написан один из самых популярных frontend фреймворков — Angular2. И его можно использовать с другими библиотеками и фреймворками, например с тем-же React. Ну а мы попробуем разобраться насколько TS применим на сервере. И так, приступим.

Установка TypeScript

TypeScript ставится как обычный npm пакет. Желательно устанавливать глобально:

После установки нам будет доступен компилятор самого языка. Проверить его версию можно командой:

Теперь можно попробовать создать какой-нибудь скрипт и его скомпилировать. Для наглядности, чтобы сразу было видно отличие от JavaScript, создадим простой класс:

Сохраним файл и запустим компилятор:

Если всё прошло удачно, то рядом мы увидим скомпилированный файл Greeting.js, а в нём примерно такой JavaScript:

Как видим — никакой магии.

TypeScript предоставляет возможность настраивать параметры компиляции. Для этого создадим файл tsconfig.json со следующим содержимым:

Мы указали самые базовые параметры. Теперь исходные файлы будут компилироваться в JavaScript версии ES6 (последняя нода его замечательно поддерживает). Более подробно о настройке компиляции можно прочитать в официальной документации.

Что с поддержкой в редакторах и IDE?

Все современные редакторы в достаточной степени поддерживают TypeScript. Лично я рекомендую Visual Studio Code от Microsoft. У него поддержка одна из лучших. Причём из коробки, что и не удивительно, т.к. всё это продукты одной компании.

Пример приложения

Теперь перейдём к созданию нашего простого примера, а заодно посмотрим как использовать TypeScript в связке с другими технологиями, которые написаны на обычном JavaScript. Наше приложение будет представлять из себя простейший API для аутентификации пользователей. Сосредоточимся исключительно на backend части. Прежде чем начнём, хотелось бы уточнить, что API не будет являться RESTful (мы будем использовать сессии) и больше подойдёт для обычный Ajax запросов. Однако саму структуру сильно упрощать не будем. Представим, что в будущем мы захотим расширить приложение.

Собственно, базовая структура проекта будет следующая:

Для простоты понимания было решено сделать структуру по типу всем известного MVC паттерна, только, как уже упоминалось выше, без использования слоя представлений.

Определение зависимостей и настройка компиляции

Добавим файл package.json со следующим содержимым:

Установим зависимости выполнив команду: npm install

Зависимостей у нас по минимуму: Express.js, модули для обработки запросов и работы с сессиями, а также библиотека NeDB для работы с БД. Для автоматизации процесса сборки и перезапуска используем команды, объявленные в блоке scripts:

npm start — будет запускать компилятор TypeScript c флагом --watch для использования кросскомпиляции.

npm run built:watch — запустит уже скомпилированный сервер через утилиту nodemon,которая будет производить перезапуск приложения при каждом изменении JS файлов.

Теперь настроим компиляцию. Создадим новый файл tsconfig.json, в котором укажем следующие параметры:

Собственно, здесь мы указываем, что хотим использовать commonjs загрузку модулей (imports будут конвертированы в require). А также, помимо прочего, директорию, где лежат исходные TypeScript файлы (src), и куда помещать скомпилированные JavaScript файлы (bin). Генерация sourceMap нам пока не нужна, поэтому мы её отключаем. А вот на опции noImplicitAny хотелось бы остановиться подробнее. Одна из особенностей TypeScript — это так называемая точечная типизация и использование внутреннего типа данных any. Суть в том, что мы не обязаны везде объявлять типы данных. А только в тех местах где это имеет смысл. Однако там, где мы не указали тип, компилятор TypeScript будет пытаться этот тип вывести. Ели у него это не получается сделать явно, то, если опция noImplicitAny  выставлена в false (по умолчанию) неявно, будет назначен тип any, иначе каждый раз при компиляции будет производиться проверка на указание типа данных any, и если мы его не укажем явно, то получим ошибку компиляции.

Благодаря наличию таких опций, TypeScript уже из коробки поддерживает линтинг кода. Кроме того, существует отдельное расширение — tslint, которое позволяет ещё более гибко настроить различного рода проверки как в процессе компиляции, так и на лету.

Типизация сторонних библиотек

Если с типизацией наших исходных кодов на TypeScript всё более-менее ясно, то как нам использовать типы в сторонних библиотеках, написанных на JavaScript? К счастью, community давно решило этот вопрос создав вот этот замечательный репозиторий: https://github.com/DefinitelyTyped/DefinitelyTyped

В этом репозитории собраны и описаны типы для самых популярных библиотек и фреймворков, которые можно подключить к проектам на TypeScript. С помощью чего, собственно, происходит описание и добавление внешней библиотеки? Для решения этой задачи у TypeScript существуют так называемые декларативные файлы, имеющие расширение *.d.ts. Данные файлы служат для декларации (описания без реализации) различных компонентов внешней подключаемой библиотеки. Такими компонентами могут быть интерфейсы, переменные, константы, функции и т.д., которые описаны уже с использованием типов TypeScript. Например, так выглядит файл деклараций для Express.js:  https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/express/index.d.ts Естественно, мы можем сами создать .d.ts файл для какой-нибудь библиотеки, описать компоненты и  подключить её в свой проект. Вдогонку ко всему вышесказанному хочу порекомендовать ресурс для быстрого поиска по доступным декларациям различных библиотек и фреймворков: http://microsoft.github.io/TypeSearch/

Теперь мы можем добавить типизацию для наших зависимостей. Так как проверка типов нам нужна только в процессе разработки, то устанавливаем типы как devDependencies:

По умолчанию TypeScript будет искать определения типов в директории node_modules/@types. Разумеется, это можно настроить в tsconfig.json, через параметр typeRoots.

С настройкой вроде бы разобрались. Теперь перейдём к самому приложению. Создадим главный класс:

Главный класс реализован как singleton. Здесь мы подключаем провайдеры и маршрутизацию, создаём экземпляр Express и запускаем приложение. Обратите внимание на то, как импортируются модули, а именно Express. Мы импортируем все компоненты фреймворка в виде функции express, а для типизации этих компонентов только необходимые интерфейсы. Вообще, интерфейсы довольно мощная штука. Например, конфигурация приложения вынесена в отдельный типизированный js объект,  описанный через интерфейс IApplicationConfig. Ещё одной интересной особенностью TypeScript, которую мы использовали, является возможность определить поле класса для передаваемого аргумента непосредственно в сигнатуре конструктора (собственно, так мы конфигурацию и подключаем).

Маршрутизация и контроллеры

Далее нам нужно добавить маршрутизацию и контроллеры. Начнём с интерфейсов маршрутизаторов. Как вы уже могли обратить внимание, интерфейс конфигурации приложения мы подключали из директории core. Добавим туда же два интерфейса, которые описывают маршрутизацию.

IApplicationRoute:

IPathRoute:

Это всего лишь демонстрация использования интерфейсов в TypeScript. С помощью первого описан непосредственно сам объект, который будет создавать и возвращать маршрут. С помощью второго — объект через который будет связан маршрутизатор и корневая точка входа. Подключение корневых маршрутов реализовано в классе AppRoutes.ts.

Далее создадим маршрутизатор пользователя:

Как мы видим, сам по себе маршрутизатор не является классом — это обычный объект, но типизированный с помощью интерфейса. Такие конструкции бывает довольно удобно использовать и здесь это показано исключительно для наглядности. Можете обратить внимание что в обработчиках маршрутов в качестве middleware мы используем методы отдельных контроллеров. Вот так у нас будет выглядеть AuthController:

Модели и провайдеры данных

Теперь перейдём к вопросу хранения и представления данных пользователей. Начнём с провайдера данных. Создадим базовый абстрактный провайдер данных:

TypeScript позволяет использовать абстрактные классы, что собственно мы и сделали в провайдере данных. Что такое абстрактный класс, я думаю, вы прекрасно знаете и без меня. А вот о хранилище данных NeDB, которое мы используем, хотелось бы рассказать подробнее.

NeDB — это встраиваемая NoSQL база данных, написанная на JavaScript с похожим на MongoDB API. БД не имеет бинарных зависимостей. Может быть использована как на клиенте, так и на сервере, а также на платформах NW.js и Electron. Устанавливается как обычный npm пакет или через bower, а данные может хранить в оперативной памяти. При хранении на диске используется JSON.  Ещё одной интересной особенностью является достаточно быстрая скорость работы, для embedded базы. Привожу данные из официальной документации:

  • Insert: 10,680 ops/s
  • Find: 43,290 ops/s
  • Update: 8,000 ops/s
  • Remove: 11,750 ops/s

Иcпользует довольно простой и понятный синтаксис для работы с данными. Вот, например, реализация провайдера пользователя, в котором каждый из методов является обёрткой над методом экземпляра хранилища NeDB:

Механизм наследования TypeScript требует явного вызова метода super() в наследниках. Поэтому при создании экземпляра класса мы передаём наименование хранилища (фактически имя файла) в родительский конструктор: super('User'). Если хранилище с данными на диске не будет обнаружено, то создастся новый файл.  Так как провайдер унаследован от абстрактного класса, мы реализовали метод onLoadStore, в котором обрабатываем возможную ошибку при загрузке хранилища. Вы также можете обратить внимание на то, как объявлены callback функции в сигнатурах методов. Такой способ объявления позволяет нам типизировать любую callback функцию, при этом никто не запрещает вынести её определение в отдельный интерфейс. Для создания и хранения провайдеров реализован класс AppDataProviders, экземпляр которого создаётся один раз при старте приложения и доступен глобально через геттер главного класса (см. UserController). Таким образом мы сможем и дальше расширять слой для работы с данными используя провайдеры-обёртки над NeDB.

Смотрим, что получилось

Для запуска приложения нам понадобится отдельный стартовый скрипт index.ts:

Теперь попробуем скомпилировать это всё и запустить. Сначала выполним команду: npm start. Если ошибок нет, запустим в той же директории ещё один сеанс консоли и выполним npm run built:watch. Результат должен быть таким:

После компиляции директория с проектом будет выглядеть следующим образом:

В директории bin находится наше скомпилированное приложение, а в директории db появился файл хранилища. Попробуем проверить работоспособность. Для тестирования запросов я буду использовать приложение POSTMAN.

Убедимся, что сервер запущен и попробуем перейти по адресу выполнив GET запрос: http://localhost:8080/user

В ответе видим 401 статус. Значит всё ok, ведь мы не зарегистрировались. Пробуем регистрироваться, но уже через POST запрос: http://localhost:8080/user/add

В ответе на регистрацию получаем объект нового пользователя.

Теперь можно пройти аутентификацию. Выполняем POST запрос: http://localhost:8080/user/login:

Вроде бы всё работает) Теперь можно запросить список пользователей выполнив GET запрос: http://localhost:8080/user

Заключение

Нашей целью было понять, можно ли использовать TypeScript при разработке серверных приложений, и насколько это удобно. Даже на таком небольшом примере  мы убедились, что TypeScript уже вполне зрелая технология, позволяющая использовать существующий инструментарий платформы Node.js без особых проблем. Язык активно развивается и поддерживается сообществом. В нём появляются новые фичи, которые по-настоящему облегчают разработку. Те особенности, которые были рассмотрены в этой статье, являются лишь малой частью того, что предоставляет нам TypeScript. Поэтому в будущем хотелось бы вернуться к этой теме. Надеюсь, и вы нашли что-то полезное для себя. Спасибо за внимание)

Ссылка на репозиторий с исходниками: https://github.com/GusNitrous/TypeScriptBackend

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *