Framework
Installation
Deepkit Framework basiert auf Runtime Types in Deepkit Type. Stelle sicher, dass @deepkit/type
korrekt installiert ist. Siehe dazu Runtime Type Installation.
npm install ts-node @deepkit/framework
Stellen Sie sicher, dass alle Peer-Abhängigkeiten installiert sind. Standardmäßig werden sie von NPM 7+ automatisch installiert.
Um Ihre Anwendung zu kompilieren, benötigen wir den TypeScript-Compiler und empfehlen ts-node
, um die App einfach auszuführen.
Eine Alternative zur Verwendung von ts-node
besteht darin, den Quellcode mit dem TypeScript-Compiler zu kompilieren und den JavaScript-Quellcode direkt auszuführen. Dies hat den Vorteil, dass sich die Ausführungsgeschwindigkeit für kurze Befehle drastisch erhöht. Allerdings wird dadurch auch zusätzlicher Workflow-Overhead erzeugt, indem der Compiler entweder manuell ausgeführt oder ein Watcher eingerichtet wird. Aus diesem Grund wird in dieser Dokumentation in allen Beispielen ts-node
verwendet.
Erste Applikation
Da das Deepkit Framework keine Konfigurationsdateien oder eine spezielle Ordnerstruktur verwendet, können Sie Ihr Projekt so strukturieren, wie Sie es wünschen. Die einzigen beiden Dateien, die Sie für den Start benötigen, sind die TypeScript-Datei app.ts und die TypeScript-Konfiguration tsconfig.json.
Unser Ziel ist es, die folgenden Dateien in unserem Projektordner zu haben:
.
├── app.ts
├── node_modules
├── package-lock.json
└── tsconfig.json
Datei: tsconfig.json
{
"compilerOptions": {
"outDir": "./dist",
"experimentalDecorators": true,
"strict": true,
"esModuleInterop": true,
"target": "ES2020",
"module": "CommonJS",
"moduleResolution": "node"
},
"reflection": true,
"files": [
"app.ts"
]
}
Datei: app.ts
#!/usr/bin/env ts-node-script
import { App } from '@deepkit/app';
import { Logger } from '@deepkit/logger';
import { cli, Command } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
@cli.controller('test')
export class TestCommand implements Command {
constructor(protected logger: Logger) {
}
async execute() {
this.logger.log('Hello World!');
}
}
new App({
controllers: [TestCommand],
imports: [new FrameworkModule]
}).run();
In diesem Code sehen Sie, dass wir einen Testbefehl über die Klasse TestCommand
definiert und eine neue Anwendung erstellt haben, die wir direkt mit run()
ausführen. Durch das Ausführen dieses Skripts starten wir die App.
Mit dem Shebang in der ersten Zeile (#!…
) können wir unser Skript mit dem folgenden Befehl ausführbar machen.
chmod +x app.ts
Und dann ausführen:
$ ./app.ts
VERSION
Node
USAGE
$ ts-node-script app.ts [COMMAND]
TOPICS
debug
migration Executes pending migration files. Use migration:pending to see which are pending.
server Starts the HTTP server
COMMANDS
test
Um nun unseren Testbefehl auszuführen, führen wir folgenden Befehl aus.
$ ./app.ts test
Hello World
In Deepkit Framework geschieht nun alles über diese app.ts
. Sie können die Datei beliebig umbennen oder weitere anlegen. Eigene CLI commands, HTTP/RPC server, Migration commands, usw werden alle über diesen Einstiegspunkt gestartet.
Um den HTTP/RPC-Server zu starten, führen Sie folgendes aus:
./app.ts server:start
App
Über das App
-Objekt startet wie Applikation.
Die run()
-Methode list dabei die Argumente aus und führt den entsprechenden CLI-Controller aus. Da FrameworkModule
eigene CLI-Controller bereitstellt, die zum Beispiel für das Starten des HTTP-Servers verantwortlich sind, können diese darüber aufgerufen werden.
Über das App
-Objekt kann auch der Dependency Injection Container angesprochen werden, ohne dass ein CLI-Controller ausgeführt wird.
const app = new App({
controllers: [TestCommand],
imports: [new FrameworkModule]
});
//get access to all registered services
const eventDispatcher = app.get(EventDispatcher);
//then run the app, or do something else
app.run();
Module
Deepkit Framework ist hochgradig modular und ermöglicht es Ihnen, Ihre Anwendung in mehrere praktische Module aufzuteilen. Jedes Modul hat seine eigene Dependency Injektion Sub-Container, Konfiguration, Befehle und vieles mehr. Im Kapitel "Erste Applikation" haben Sie bereits ein Modul erstellt - das Root-Modul. new App
benötigt fast die gleichen Argumente wie ein Modul, denn es erstellt das Root-Modul im Hintergrund für Sie automatisch.
Sie können dieses Kapitel überspringen, wenn Sie nicht vorhaben, Ihre Anwendung in Untermodule aufzuteilen, oder wenn Sie nicht vorhaben, ein Modul als Paket für andere zur Verfügung zu stellen.
Ein Modul ist eine einfache Klasse:
import { createModule } from '@deepkit/app';
export class MyModule extends createModule({}) {
}
Es hat zu diesem Zeitpunkt im Grunde keine Funktionalität, da seine Moduldefinition ein leeres Objekt ist und es keine Methoden hat, aber dies demonstriert die Beziehung zwischen Modulen und Ihrer Anwendung (Ihrem Stammmodul). Dieses Modul MyModule kann dann in Ihrer Anwendung oder in anderen Modulen importiert werden.
import { MyModule } from './module.ts'
new App({
imports: [
new MyModule(),
]
}).run();
Sie können nun diesem Modul Features hinzufügen, wie Sie es mit App
tun würden. Die Argumente sind die gleichen, nur dass Importe in einer Moduldefinition nicht verfügbar sind. Fügen Sie HTTP/RPC/CLI-Controller, Dienste, eine Konfiguration, Event-Listener sowie verschiedene Modul-Hooks hinzu, um Module dynamischer zu gestalten.
Controllers
Module können Controller definieren, die von anderen Modulen verarbeitet werden. Wenn Sie zum Beispiel einen Controller mit Dekoratoren aus dem @deepkit/http
-Paket hinzufügen, wird sein Modul HttpModule
dies aufgreifen und die gefundenen Routen in seinem Router registrieren. Ein einzelner Controller kann mehrere solcher Dekoratoren enthalten. Es liegt an dem Modulautor, der Ihnen diese Dekoratoren gibt, wie er die Controller verarbeitet.
In Deepkit gibt es drei Pakete, die solche Controller verarbeitet: HTTP, RPC, und CLI. Siehe jeweils deren Kapitel, um mehr zu erfahren. Nachfolgend ist ein Beispiel eines HTTP-Controllers:
import { createModule } from '@deepkit/app';
import { http } from '@deepkit/http';
import { injectable } from '@deepkit/injector';
class MyHttpController {
@http.GET('/hello)
hello() {
return 'Hello world!';
}
}
export class MyModule extends createModule({
controllers: [MyHttpController]
}) {}
//same is possible for App
new App({
controllers: [MyHttpController]
}).run();
Provider
Wenn Sie einen Provider im providers
-Bereich Ihrer Anwendung definieren, ist dieser in Ihrer gesamten Anwendung zugänglich. Bei Modulen hingegen werden diese Provider automatisch in den Subcontainer für die Injektion von Abhängigkeiten dieses Moduls gekapselt. Sie müssen jeden Provider manuell exportieren, um ihn für ein anderes Modul bzw. ihrer Anwendung verfügbar zu machen.
Um mehr darüber zu erfahren, wie Provider funktionieren, lesen Sie bitte das Kapitel Dependency Injection.
import { createModule } from '@deepkit/app';
import { http } from '@deepkit/http';
import { injectable } from '@deepkit/injector';
export class HelloWorldService {
helloWorld() {
return 'Hello there!';
}
}
class MyHttpController {
constructor(private helloService: HelloWorldService) {}
@http.GET('/hello)
hello() {
return this.helloService.helloWorld();
}
}
export class MyModule extends createModule({
controllers: [MyHttpController],
providers: [HelloWorldService],
}) {}
//same is possible for App
new App({
controllers: [MyHttpController],
providers: [HelloWorldService],
}).run();
Wenn ein Benutzer dieses Modul importiert, hat er keinen Zugriff auf HelloWorldService
, da dieser im Subdependency-Injection-Container von MyModule
gekapselt ist.
Exports
Um Provider im Modul des Importeurs verfügbar zu machen, können Sie den Token des Providers in exports
aufnehmen. Dadurch wird der Provider im Wesentlichen eine Ebene nach oben in den Dependency-Injection-Container des übergeordneten Moduls - des Importeurs - verschoben.
import { createModule } from '@deepkit/app';
export class MyModule extends createModule({
controllers: [MyHttpController]
providers: [HelloWorldService],
exports: [HelloWorldService],
}) {}
Wenn Sie andere Provider wie FactoryProvider
, UseClassProvider
usw. haben, sollten Sie trotzdem nur den Klassentyp in den Exporten verwenden.
import { createModule } from '@deepkit/app';
export class MyModule extends createModule({
controllers: [MyHttpController]
providers: [
{provide: HelloWorldService, useValue: new HelloWorldService}
],
exports: [HelloWorldService],
}) {}
We can now import that module and use its exported service in our application code.
#!/usr/bin/env ts-node-script
import { App } from '@deepkit/app';
import { cli, Command } from '@deepkit/app';
import { HelloWorldService, MyModule } from './my-module';
@cli.controller('test')
export class TestCommand implements Command {
constructor(protected helloWorld: HelloWorldService) {
}
async execute() {
this.helloWorld.helloWorld();
}
}
new App({
controllers: [TestCommand],
imports: [
new MyModule(),
]
}).run();
Lesen Sie das Kapitel Dependency Injection um mehr darüber zu erfahren.
Konfiguration
Im Deepkit Framework können Module und Ihre Anwendung über Konfigurationsoptionen verfügen. Eine Konfiguration kann z.B. aus Datenbank-URLs, Passwörtern, IPs usw. bestehen. Services, HTTP/RPC/CLI Controller sowie Template Funktionen können diese Konfigurationsoptionen über Dependency Injection auslesen.
Eine Konfiguration kann durch die Definition einer Klasse mit Eigenschaften definiert werden. Dies ist ein typsicherer Weg, um eine Konfiguration für Ihre gesamte Anwendung zu definieren, und ihre Werte werden automatisch serialisiert und validiert.
Beispiel
import { MinLength } from '@deepkit/type';
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { http } from '@deepkit/http';
class Config {
pageTitle: string & MinLength<2> = 'Cool site';
domain: string = 'example.com';
debug: boolean = false;
}
class MyWebsite {
constructor(protected allSettings: Config) {
}
@http.GET()
helloWorld() {
return 'Hello from ' + this.allSettings.pageTitle + ' via ' + this.allSettings.domain;
}
}
new App({
config: Config,
controllers: [MyWebsite],
imports: [new FrameworkModule]
}).run();
$ curl http://localhost:8080/
Hello from Cool site via example.com
Konfigurationsklasse
import { MinLength } from '@deepkit/type';
export class Config {
title!: string & MinLength<2>; //this makes it required and needs to be provided
host?: string;
debug: boolean = false; //default values are supported as well
}
import { createModule } from '@deepkit/app';
import { Config } from './module.config.ts';
export class MyModule extends createModule({
config: Config
}) {}
Die Werte für die Konfigurationsoptionen können entweder im Konstruktor des Moduls, mit der Methode .configure()
oder über Konfigurationslader (z.B. Umgebungsvariablenlader) bereitgestellt werden.
import { MyModule } from './module.ts';
new App({
imports: [new MyModule({title: 'Hello World'}],
}).run();
Um die Konfigurationsoptionen eines importierten Moduls dynamisch zu ändern, können Sie den process
Hook verwenden. Dies ist ein guter Ort, um entweder Konfigurationsoptionen umzuleiten oder ein importiertes Modul abhängig von der aktuellen Modulkonfiguration oder anderen Modulinstanzinformationen einzurichten.
import { MyModule } from './module.ts';
export class MainModule extends createModule({
}) {
process() {
this.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
}
}
Auf der Anwendungsebene funktioniert es etwas anders:
new App({
imports: [new MyModule({title: 'Hello World'}],
})
.setup((module, config) => {
module.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
})
.run();
Wenn das Root-Anwendungsmodul aus einem regulären Modul erstellt wird, funktioniert es ähnlich wie reguläre Module.
class AppModule extends createModule({
}) {
process() {
this.getImportedModuleByClass(MyModule).configure({title: 'Changed'});
}
}
App.fromModule(new AppModule()).run();
Konfigurationsoptionen Auslesen
Um eine Konfigurationsoption in einem Dienst zu verwenden, können Sie die normale Dependency Injection verwenden. Es ist möglich, entweder das gesamte Konfigurationsobjekt, einen einzelnen Wert oder einen Teil der Konfiguration zu injizieren.
Partial
Um nur einen Teilbereich der Konfigurationswerte zu injizieren, verwenden Sie den Typ Pick
.
import { Config } from './module.config';
export class MyService {
constructor(private config: Pick<Config, 'title' | 'host'}) {
}
getTitle() {
return this.config.title;
}
}
//In unit tests, it can be instantiated via
new MyService({title: 'Hello', host: '0.0.0.0'});
//or you can use type aliases
type MyServiceConfig = Pick<Config, 'title' | 'host'};
export class MyService {
constructor(private config: MyServiceConfig) {
}
}
Debugger
Die Konfigurationswerte Ihrer Anwendung und aller Module können im Debugger angezeigt werden. Aktivieren Sie die Debug-Option im FrameworkModul
und öffnen Sie http://localhost:8080/_debug/configuration
.
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
new App({
config: Config,
controllers: [MyWebsite],
imports: [
new FrameworkModule({
debug: true,
})
]
}).run();

Sie können auch ts-node app.ts app:config
verwenden, um alle verfügbaren Konfigurationsoptionen, den aktiven Wert, ihren Standardwert, die Beschreibung und den Datentyp anzuzeigen.
$ ts-node app.ts app:config
Application config
┌─────────┬───────────────┬────────────────────────┬────────────────────────┬─────────────┬───────────┐
│ (index) │ name │ value │ defaultValue │ description │ type │
├─────────┼───────────────┼────────────────────────┼────────────────────────┼─────────────┼───────────┤
│ 0 │ 'pageTitle' │ 'Other title' │ 'Cool site' │ '' │ 'string' │
│ 1 │ 'domain' │ 'example.com' │ 'example.com' │ '' │ 'string' │
│ 2 │ 'port' │ 8080 │ 8080 │ '' │ 'number' │
│ 3 │ 'databaseUrl' │ 'mongodb://localhost/' │ 'mongodb://localhost/' │ '' │ 'string' │
│ 4 │ 'email' │ false │ false │ '' │ 'boolean' │
│ 5 │ 'emailSender' │ undefined │ undefined │ '' │ 'string?' │
└─────────┴───────────────┴────────────────────────┴────────────────────────┴─────────────┴───────────┘
Modules config
┌─────────┬──────────────────────────────┬─────────────────┬─────────────────┬────────────────────────────────────────────────────────────────────────────────────────────────────┬────────────┐
│ (index) │ name │ value │ defaultValue │ description │ type │
├─────────┼──────────────────────────────┼─────────────────┼─────────────────┼────────────────────────────────────────────────────────────────────────────────────────────────────┼────────────┤
│ 0 │ 'framework.host' │ 'localhost' │ 'localhost' │ '' │ 'string' │
│ 1 │ 'framework.port' │ 8080 │ 8080 │ '' │ 'number' │
│ 2 │ 'framework.httpsPort' │ undefined │ undefined │ 'If httpsPort and ssl is defined, then the https server is started additional to the http-server.' │ 'number?' │
│ 3 │ 'framework.selfSigned' │ undefined │ undefined │ 'If for ssl: true the certificate and key should be automatically generated.' │ 'boolean?' │
│ 4 │ 'framework.keepAliveTimeout' │ undefined │ undefined │ '' │ 'number?' │
│ 5 │ 'framework.path' │ '/' │ '/' │ '' │ 'string' │
│ 6 │ 'framework.workers' │ 1 │ 1 │ '' │ 'number' │
│ 7 │ 'framework.ssl' │ false │ false │ 'Enables HTTPS server' │ 'boolean' │
│ 8 │ 'framework.sslOptions' │ undefined │ undefined │ 'Same interface as tls.SecureContextOptions & tls.TlsOptions.' │ 'any' │
...
Konfigurationswerte setzen
Standardmäßig werden keine Werte überschrieben, es werden also Standardwerte verwendet. Es gibt mehrere Möglichkeiten, Konfigurationswerte zu setzen.
-
Umgebungsvariablen für jede Option
-
Umgebungsvariable über JSON
-
dotenv-Dateien
Sie können mehrere Methoden zum Laden der Konfiguration gleichzeitig verwenden. Die Reihenfolge, in der sie aufgerufen werden, ist dabei wichtig.
Environment variables
Um die Einstellung jeder Konfigurationsoption über eine eigene Umgebungsvariable zu ermöglichen, verwenden Sie loadConfigFromEnv
. Das Standardpräfix ist APP_
, aber Sie können es ändern. Es lädt auch automatisch .env
-Dateien. Standardmäßig wird eine Benennungsstrategie mit Großbuchstaben verwendet, aber auch das können Sie ändern.
Für Konfigurationsoptionen wie oben pageTitle
, können Sie APP_PAGE_TITLE="Anderer Titel"
verwenden, um den Wert zu verändern.
new App({
config: config,
controllers: [MyWebsite],
})
.loadConfigFromEnv({prefix: 'APP_'})
.run();
APP_PAGE_TITLE="Other title" ts-node app.ts server:start
JSON environment variable
Um mehrere Konfigurationsoptionen über eine einzige Umgebungsvariable zu ändern, verwenden Sie loadConfigFromEnvVariable
. Das erste Argument ist der Name der Umgebungsvariablen.
new App({
config: config,
controllers: [MyWebsite],
})
.loadConfigFromEnvVariable('APP_CONFIG')
.run();
APP_CONFIG='{"pageTitle": "Other title"}' ts-node app.ts server:start
DotEnv Dateien
Um mehrere Konfigurationsoptionen über eine dotenv-Datei zu ändern, verwenden Sie loadConfigFromEnv
. Das erste Argument ist entweder ein Pfad zu einer dotenv (relativ zu cwd
) oder mehrere Pfade. Wenn es ein Array ist, wird jeder Pfad ausprobiert, bis eine vorhandene Datei gefunden wird.
new App({
config: config,
controllers: [MyWebsite],
})
.loadConfigFromEnv({envFilePath: ['production.dotenv', 'dotenv']})
.run();
$ cat dotenv
APP_PAGE_TITLE=Other title
$ ts-node app.ts server:start
Module Configuration
Jedes importierte Modul kann einen Modulnamen haben. Dieser Name wird für die oben verwendeten Konfigurationspfade verwendet.
Für die Konfiguration von Umgebungsvariablen lautet der Pfad für die FrameworkModule
-Option port beispielsweise FRAMEWORK_PORT
. Alle Namen werden standardmäßig in Großbuchstaben geschrieben. Wenn ein Präfix von APP_
verwendet wird, kann der Port über folgendes geändert werden:
$ APP_FRAMEWORK_PORT=9999 ts-node app.ts server:start
2021-06-12T18:59:26.363Z [LOG] Start HTTP server, using 1 workers.
2021-06-12T18:59:26.365Z [LOG] HTTP MyWebsite
2021-06-12T18:59:26.366Z [LOG] GET / helloWorld
2021-06-12T18:59:26.366Z [LOG] HTTP listening at http://localhost:9999/
In Dotenv-Dateien wäre es auch APP_FRAMEWORK_PORT=9999
.
In JSON-Umgebungsvariablen über loadConfigFromEnvVariable('APP_CONFIG')
hingegen ist es die Struktur der eigentlichen Konfigurationsklasse. framework
wird zu einem Objekt.
$ APP_CONFIG='{"framework": {"port": 9999}}' ts-node app.ts server:start
Dies funktioniert für alle Module gleich. Für die Konfigurationsoption Ihrer Anwendung (new App
) ist kein Modulpräfix erforderlich.
Application Server
Public Directory
Das FrameworkModule bietet eine Möglichkeit, statische Dateien wie Bilder, PDFs, Binärdateien usw. über HTTP bereitzustellen. Mit der Konfigurationsoption publicDir
können Sie angeben, welcher Ordner als Standard-Einstiegspunkt für Anfragen verwendet werden soll, die nicht zu einer HTTP-Controller-Route führen. Standardmäßig ist dieses Verhalten deaktiviert (leerer Wert).
Um die Bereitstellung öffentlicher Dateien zu aktivieren, setzen Sie publicDir
auf einen Ordner Ihrer Wahl. Normalerweise würden Sie einen Namen wie publicDir
wählen, um die Dinge offensichtlich zu machen.
.
├── app.ts
└── publicDir
└── logo.jpg
Um die Option publicDir
zu ändern, können Sie das erste Argument von FrameworkModule
ändern.
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
// your config and http controller here
new App({
config: config,
controllers: [MyWebsite],
imports: [
new FrameworkModule({
publicDir: 'publicDir'
})
]
})
.run();
Alle Dateien innerhalb dieses konfigurierten Ordners sind nun über HTTP zugänglich. Wenn Sie beispielsweise http://localhost:8080/logo.jpg
öffnen, sehen Sie das Bild logo.jpg
im Verzeichnis publicDir
.
Database
Deepkit verfügt über eine eigene leistungsstarke Datenbankabstraktionsbibliothek namens Deepkit ORM. Es handelt sich um eine ORM-Bibliothek (Object-Relational Mapping), die die Arbeit mit SQL-Datenbanken und MongoDB erleichtert.
Obwohl Sie jede beliebige Datenbankbibliothek verwenden können, empfehlen wir Deepkit ORM, da es die schnellste TypeScript-Datenbankabstraktionsbibliothek ist, die perfekt in das Deepkit-Framework integriert ist und viele Funktionen hat, die Ihren Workflow und Ihre Effizienz verbessern.
Um alle Informationen über Deepkit ORM zu erhalten, lesen Sie das Kapitel Database.
Database Klassen
Die einfachste Art, das Database
-Objekt von Deepkit ORM innerhalb der Applikation zu verwenden, ist das Registrieren einer Klasse, die davon ableitet.
import { Database } from '@deepkit/orm';
import { SQLiteDatabaseAdapter } from '@deepkit/sqlite';
import { User } from './models';
export class SQLiteDatabase extends Database {
name = 'default';
constructor() {
super(new SQLiteDatabaseAdapter('/tmp/myapp.sqlite'), [User]);
}
}
Erstellen Sie eine neue Klasse und geben Sie in ihrem Konstruktor den Adapter mit seinen Parametern an und fügen Sie dem zweiten Parameter alle Entitäten/Modelle hinzu, die mit dieser Datenbank verbunden sein sollen.
Sie können nun diese Datenbankklasse als Provider registrieren. Wir aktivieren auch migrateOnStartup
, das alle Tabellen in Ihrer Datenbank automatisch beim Bootstrap erstellt. Dies ist ideal für Rapid Prototyping, wird aber für ein ernsthaftes Projekt oder eine Produktionseinrichtung nicht empfohlen. Hier sollten dann normale Datenbank Migrationen verwendet werden.
Außerdem aktivieren wir debug
, was uns erlaubt, den Debugger zu öffnen, wenn der Server der Anwendung gestartet wird, und Ihre Datenbankmodelle direkt in seinem integrierten ORM-Browser zu verwalten.
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { SQLiteDatabase } from './database.ts';
new App({
providers: [SQLiteDatabase],
imports: [
new FrameworkModule({
migrateOnStartup: true,
debug: true,
})
]
}).run();
Sie können nun überall auf SQLiteDatabase
zugreifen, indem Sie Dependency Injection verwenden:
import { SQLiteDatabase } from './database.ts';
export class Controller {
constructor(protected database: SQLiteDatabase) {}
@http.GET()
async startPage(): Promise<User[]> {
//return all users
return await this.database.query(User).find();
}
}
Mehr Datenbanken
Sie können so viele Datenbankklassen hinzufügen, wie Sie möchten, und sie so benennen, wie Sie möchten. Achten Sie darauf, den Namen jeder Datenbank zu ändern, damit sie bei der Verwendung des ORM-Browsers nicht mit anderen in Konflikt gerät.
Daten Verwalten
Sie haben jetzt alles eingerichtet, um Ihre Datenbankdaten mit dem Deepkit ORM Browser zu verwalten. Um den ORM-Browser zu öffnen und den Inhalt zu verwalten, schreiben Sie alle Schritte von oben in die Datei app.ts
und starten den Server.
$ ts-node app.ts server:start
2021-06-11T15:08:54.330Z [LOG] Start HTTP server, using 1 workers.
2021-06-11T15:08:54.333Z [LOG] Migrate database default
2021-06-11T15:08:54.336Z [LOG] RPC DebugController deepkit/debug/controller
2021-06-11T15:08:54.337Z [LOG] RPC OrmBrowserController orm-browser/controller
2021-06-11T15:08:54.337Z [LOG] HTTP OrmBrowserController
2021-06-11T15:08:54.337Z [LOG] GET /_orm-browser/query httpQuery
2021-06-11T15:08:54.337Z [LOG] HTTP StaticController
2021-06-11T15:08:54.337Z [LOG] GET /_debug/:any serviceApp
2021-06-11T15:08:54.337Z [LOG] HTTP listening at http://localhost:8080/
You can now open http://localhost:8080/_debug/database/default.

Sie können das ER-Diagramm sehen. Im Moment ist nur eine Entität verfügbar. Wenn Sie weitere mit Beziehungen hinzufügen, sehen Sie alle Informationen auf einen Blick.
Wenn Sie in der linken Seitenleiste auf User
klicken, können Sie dessen Inhalt verwalten. Klicken Sie auf das +
-Symbol, und ändern Sie den Titel des neuen Datensatzes. Nachdem Sie die erforderlichen Werte (wie den Benutzernamen) geändert haben, klicken Sie auf "Bestätigen". Dadurch werden alle Änderungen an die Datenbank übertragen und bleiben dauerhaft bestehen. Die Autoinkrement-ID wird automatisch zugewiesen.

Mehr Lernen
Um mehr über die Funktionsweise von SQLiteDatabase
zu erfahren, lesen Sie bitte das Kapitel Database und seine Unterkapitel, wie z.B. die Abfrage von Daten, die Manipulation von Daten über Sessions, die Definition von Relationen und vieles mehr.
Bitte beachten Sie, dass sich die Kapitel dort auf die eigenständige Bibliothek @deepkit/orm
beziehen und keine Dokumentation über den Teil des Deepkit Frameworks enthalten, den Sie oben in diesem Kapitel gelesen haben. In der Standalone-Bibliothek instanziieren Sie Ihre Datenbankklasse manuell, z. B. über new SQLiteDatabase()
. In Ihrer Deepkit-Framework-Anwendung wird dies jedoch automatisch mithilfe des Dependency Injection Containers durchgeführt.
Logger
Deepkit Logger ist eine eigenständige Bibliothek mit einer primären Klasse Logger, die Sie zur Protokollierung von Informationen verwenden können. Diese Klasse wird automatisch im Dependency Injection Container Ihrer Deepkit Framework-Anwendung bereitgestellt.
Die Klasse Logger
verfügt über mehrere Methoden, die sich jeweils wie console.log
verhalten.
Name |
Log Level |
Level id |
logger.error() |
Error |
1 |
logger.warning() |
Warning |
2 |
logger.log() |
Default log |
3 |
logger.info() |
Special information |
4 |
logger.debug() |
Debug information |
5 |
Standardmäßig hat ein Logger den Level "info", d.h. er verarbeitet nur Info-Meldungen und mehr (d.h. log, warning, error, aber nicht debug). Um den Log-Level zu ändern, rufen Sie zum Beispiel logger.level = 5
auf.
Benutzen in der Anwendung
Um den Logger in Ihrer Deepkit-Framework-Anwendung zu verwenden, können Sie einfach Logger
in Ihre Services oder Controller injizieren.
import { Logger } from '@deepkit/logger';
class MyService {
constructor(protected logger: Logger) {}
doSomething() {
const value = 'yes';
this.logger.log('This is wild', value);
}
}
Farben
Der Logger unterstützt farbige Protokollmeldungen. Sie können Farben bereitstellen, indem Sie XML-Tags verwenden, die den Text umgeben, der in Farbe erscheinen soll.
const username = 'Peter';
logger.log(`Hi <green>${username}</green>`);
Bei Transportern, die keine Farben unterstützen, werden die Farbinformationen automatisch entfernt. Im Standardtransporter (ConsoleTransport
) wird die Farbe angezeigt. Die folgenden Farben sind verfügbar: black
, red
, green
, blue
, cyan
, magenta
, white
und grey
/gray
.
Transporter
Sie können einen einzelnen oder mehrere Transporter konfigurieren. In einer Deepkit Framework-Anwendung wird der Transporter ConsoleTransport
automatisch konfiguriert. Um zusätzliche Transporter zu konfigurieren, können Sie Setup Calls verwenden:
import { Logger, LoggerTransport } from '@deepkit/logger';
export class MyTransport implements LoggerTransport {
write(message: string, level: LoggerLevel, rawMessage: string) {
process.stdout.write(JSON.stringify({message: rawMessage, level, time: new Date}) + '\n');
}
supportsColor() {
return false;
}
}
new App()
.setup((module, config) => {
module.setupProvider(Logger).addTransport(new MyTransport);
})
.run();
Um alle Transporter durch eine neue Gruppe von Transportern zu ersetzen, verwenden Sie setTransport
:
import { Logger } from '@deepkit/logger';
new App()
.setup((module, config) => {
module.setupProvider(Logger).setTransport([new MyTransport]);
})
.run();
import { Logger, JSONTransport } from '@deepkit/logger';
new App()
.setup((module, config) => {
module.setupProvider(Logger).setTransport([new JSONTransport]);
})
.run();
Formatter
Mit Formatierern können Sie das Nachrichtenformat ändern, z. B. den Zeitstempel hinzufügen. Wenn eine Anwendung über server:start
gestartet wird, wird automatisch ein DefaultFormatter
hinzugefügt (der Zeitstempel, Bereich und Protokollstufe hinzufügt), wenn kein anderer Formatter vorhanden ist.
Scoped Logger
Scoped Logger fügen jedem Protokolleintrag einen beliebigen Bereichsnamen hinzu, der hilfreich sein kann, um festzustellen, aus welchem Teilbereich Ihrer Anwendung der Protokolleintrag stammt.
const scopedLogger = logger.scoped('database');
scopedLogger.log('Query', query);
JSON Transporter
Um die Ausgabe in JSON-Protokolle zu ändern, können Sie den mitgelieferten JSONTransport
verwenden.
Context Data
Um einem Protokolleintrag kontextbezogene Daten hinzuzufügen, fügen Sie ein einfaches Objektliteral als letztes Argument hinzu. Nur Protokollaufrufe mit mindestens zwei Argumenten können kontextbezogene Daten enthalten.
const query = 'SELECT *';
const user = new User;
logger.log('Query', {query, user}); //last argument is context data
logger.log('Another', 'wild log entry', query, {user}); //last argument is context data
logger.log({query, user}); //this is not handled as context data.
Events
Deepkit Framework kommt mit diversen Event-Tokens, auf die Event-Listener registriert werden können.
Siehe das Kapitel Events, um mehr darüber zu erfahren, wie Events funktionieren.
Dispatch Events
Events werden über die Klasse EventDispatcher
gesendet. In einer Deepkit Framework Applikation kann dieser über Dependency Injection bereitgestellt werden.
import { cli, Command } from '@deepkit/app';
import { EventDispatcher } from '@deepkit/event';
@cli.controller('test')
export class TestCommand implements Command {
constructor(protected eventDispatcher: EventDispatcher) {
}
async execute() {
this.eventDispatcher.dispatch(UserAdded, new UserEvent({ username: 'Peter' }));
}
}
Event Listener
Es gibt zwei Arten auf Events zu reagieren. Entweder über Controller Klassen oder reguläre Funktionen.
Beide werden in der App oder in Modulen unter listeners
registriert.
Controller Listener
import { eventDispatcher } from '@deepkit/event';
class MyListener {
@eventDispatcher.listen(UserAdded)
onUserAdded(event: typeof UserAdded.event) {
console.log('User added!', event.user.username);
}
}
new App({
listeners: [MyListener],
}).run();
Functional Listener
new App({
listeners: [
UserAdded.listen((event) => {
console.log('User added!', event.user.username);
});
],
}).run();
Framework Events
Deepkit Framework selbst hat mehrere Ereignisse aus dem Anwendungsserver, auf die Sie hören können.
Functional Listener
import { onServerMainBootstrap } from '@deepkit/framework';
new App({
listeners: [
onServerMainBootstrap.listen((event) => {
console.log('User added!', event.user.username);
});
],
}).run();
Name | Description |
---|---|
onServerBootstrap |
Called only once for application server bootstrap (for main process and workers). |
onServerBootstrapDone |
Called only once for application server bootstrap (for main process and workers) as soon as the application server has started. |
onServerMainBootstrap |
Called only once for application server bootstrap (in the main process). |
onServerMainBootstrapDone |
Called only once for application server bootstrap (in the main process) as soon as the application server has started |
onServerWorkerBootstrap |
Called only once for application server bootstrap (in the worker process). |
onServerWorkerBootstrapDone |
Called only once for application server bootstrap (in the worker process) as soon as the application server has started. |
ServerShutdownEvent |
Called when application server shuts down (in master process and each worker). |
onServerMainShutdown |
Called when application server shuts down in the main process. |
onServerWorkerShutdown |
Called when application server shuts down in the worker process. |
Deployment
In diesem Kapitel erfahren Sie, wie Sie Ihre Anwendung in JavaScript kompilieren, für Ihre Produktionsumgebung konfigurieren und über Docker bereitstellen können.
TypeScript kompilieren
Nehmen wir an, Sie haben eine Anwendung wie diese in einer Datei app.ts
:
#!/usr/bin/env ts-node-script
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
import { http } from '@deepkit/http';
class Config {
title: string = 'DEV my Page';
}
class MyWebsite {
constructor(protected title: Config['title']) {
}
@http.GET()
helloWorld() {
return 'Hello from ' + this.title;
}
}
new App({
config: Config,
controllers: [MyWebsite],
imports: [new FrameworkModule]
})
.loadConfigFromEnv()
.run();
Wenn Sie ts-node app.ts server:start
verwenden, sehen Sie, dass alles korrekt funktioniert. In einer Produktionsumgebung würden Sie den Server in der Regel nicht mit ts-node
starten. Sie würden ihn in JavaScript kompilieren und dann den Node verwenden. Dazu müssen Sie eine korrekte tsconfig.json
mit den richtigen Konfigurationsoptionen haben. In der Sektion "Erste Applikation" ist Ihre tsconfig.json
so konfiguriert, dass sie JavaScript im Ordner ./dist
ausgibt. Wir gehen davon aus, dass Sie das auch so konfiguriert haben.
Wenn alle Compiler-Einstellungen korrekt sind und Ihr outDir
auf einen Ordner wie z.B. dist
zeigt, dann werden, sobald Sie den Befehl tsc
in Ihrem Projekt ausführen, alle Ihre verlinkten Dateien in den Dateien in der tsconfig.json
zu JavaScript kompiliert. Es reicht, wenn Sie Ihre Einstiegsdateien in dieser Liste angeben. Alle importierten Dateien werden ebenfalls automatisch kompiliert und müssen nicht explizit in die tsconfig.json
eingefügt werden. tsc
ist Teil von Typescript, wenn Sie npm install typescript
installieren.
$ ./node_modules/.bin/tsc
Der TypeScript-Compiler gibt nichts aus, wenn er erfolgreich war. Sie können die Ausgabe von dist
jetzt überprüfen.
$ tree dist
dist
└── app.js
Sie sehen, dass es nur eine Datei gibt. Sie können sie über node dist/app.js
ausführen und erhalten die gleiche Funktionalität wie mit ts-node app.ts
.
Für ein Deployment ist es wichtig, dass die TypeScript-Dateien korrekt kompiliert werden und alles direkt über Node funktioniert. Sie könnten nun einfach Ihren dist
-Ordner einschließlich Ihrer node_modules
verschieben und node dist/app.js server:start
ausführen und Ihre App ist erfolgreich deployed. Sie würden jedoch andere Lösungen wie Docker verwenden, um Ihre Anwendung korrekt zu verpacken.
Konfiguration
In einer Produktionsumgebung würden Sie den Server nicht an localhost
binden, sondern höchstwahrscheinlich an alle Geräte über 0.0.0.0
. Wenn Sie nicht hinter einem Reverse-Proxy stehen, würden Sie auch den Port auf 80 einstellen. Um diese beiden Einstellungen zu konfigurieren, müssen Sie das FrameworkModule
anpassen. Die beiden Optionen, die uns interessieren, sind host
und port
. Damit sie von außen über Umgebungsvariablen oder über .dotenv-Dateien konfiguriert werden können, müssen wir dies zunächst zulassen. Glücklicherweise hat unser obiger Code dies bereits mit der Methode loadConfigFromEnv()
getan.
Bitte lesen Sie das Kapitel Konfiguration, um mehr darüber zu erfahren, wie Sie die Konfigurationsoptionen der Anwendung einstellen können.
Um zu sehen, welche Konfigurationsoptionen verfügbar sind und welchen Wert sie haben, können Sie den Befehl ts-node app.ts app:config
verwenden. Sie können sie auch im Framework-Debugger sehen.
SSL
Es wird empfohlen (und manchmal auch vorgeschrieben), Ihre Anwendung über HTTPS mit SSL laufen zu lassen. Es gibt mehrere Optionen zur Konfiguration von SSL. Um SSL zu aktivieren, verwenden Sie
framework.ssl
und konfigurieren Sie dessen Parameter mit den folgenden Optionen.
Name | Type | Description |
---|---|---|
framework.ssl |
boolean |
Enables HTTPS server when true |
framework.httpsPort |
number? |
If httpsPort and ssl is defined, then the https server is started additional to the http server. |
framework.sslKey |
string? |
A file path to a ssl key file for https |
framework.sslCertificate |
string? |
A file path to a certificate file for https |
framework.sslCa |
string? |
A file path to a ca file for https |
framework.sslCrl |
string? |
A file path to a crl file for https |
framework.sslOptions |
object? |
Same interface as tls.SecureContextOptions & tls.TlsOptions. |
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
// your config and http controller here
new App({
config: Config,
controllers: [MyWebsite],
imports: [
new FrameworkModule({
ssl: true,
selfSigned: true,
sslKey: __dirname + 'path/ssl.key',
sslCertificate: __dirname + 'path/ssl.cert',
sslCA: __dirname + 'path/ssl.ca',
})
]
})
.run();
Local SSL
In der lokalen Entwicklungsumgebung können Sie selbstsignierte HTTPs mit der Option framework.selfSigned
aktivieren.
import { App } from '@deepkit/app';
import { FrameworkModule } from '@deepkit/framework';
// your config and http controller here
new App({
config: config,
controllers: [MyWebsite],
imports: [
new FrameworkModule({
ssl: true,
selfSigned: true,
})
]
})
.run();
$ ts-node app.ts server:start
2021-06-13T18:04:01.563Z [LOG] Start HTTP server, using 1 workers.
2021-06-13T18:04:01.598Z [LOG] Self signed certificate for localhost created at var/self-signed-localhost.cert
2021-06-13T18:04:01.598Z [LOG] Tip: If you want to open this server via chrome for localhost, use chrome://flags/#allow-insecure-localhost
2021-06-13T18:04:01.606Z [LOG] HTTP MyWebsite
2021-06-13T18:04:01.606Z [LOG] GET / helloWorld
2021-06-13T18:04:01.606Z [LOG] HTTPS listening at https://localhost:8080/
Wenn Sie diesen Server jetzt starten, ist Ihr HTTP-Server als HTTPS unter https://localhost:8080/
verfügbar. In Chrome erhalten Sie beim Öffnen dieser URL jetzt die Fehlermeldung "NET::ERR_CERT_INVALID", da selbstsignierte Zertifikate als Sicherheitsrisiko gelten: chrome://flags/#allow-insecure-localhost
.
Testing
Die Services und Controller im Deepkit Framework sind so konzipiert, dass sie SOLID und sauberen Code unterstützen, der gut konzipiert, gekapselt und getrennt ist. Diese Eigenschaften machen den Code einfach zu testen.
Diese Dokumentation zeigt Ihnen, wie Sie ein Test-Framework namens Jest mit ts-jest
einrichten können. Führen Sie dazu den folgenden Befehl aus, um jest
und ts-jest
zu installieren.
npm install jest ts-jest @types/jest
Jest benötigt ein paar Konfigurationsoptionen, um zu wissen, wo die Testanzüge zu finden sind und wie der TS-Code zu kompilieren ist. Fügen Sie die folgende Konfiguration zu Ihrer package.json
hinzu:
{
...,
"jest": {
"transform": {
"^.+\\.(ts|tsx)$": "ts-jest"
},
"testEnvironment": "node",
"resolver": "@deepkit/framework/resolve",
"testMatch": [
"**/*.spec.ts"
]
}
}
Ihre Testdateien sollten den Namen *.spec.ts
tragen. Erstellen Sie eine Datei test.spec.ts
mit folgendem Inhalt.
test('first test', () => {
expect(1 + 1).toBe(2);
});
Mit dem Befehl jest können Sie nun alle Ihre Testanzüge auf einmal ausführen.
$ node_modules/.bin/jest
PASS ./test.spec.ts
✓ first test (1 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 0.23 s, estimated 1 s
Ran all test suites.
Bitte lesen Sie die Jest-Dokumentation, um mehr darüber zu erfahren, wie das Jest CLI-Tool funktioniert und wie Sie anspruchsvollere Tests und ganze Test-Suites schreiben können.
Unit Test
Wann immer möglich sollten Sie Ihre Services mit einem Unit-Test teste. Je einfacher, besser getrennt und besser definiert Ihre Service-Abhängigkeiten sind, desto einfacher ist es, sie zu testen. In diesem Fall können Sie einfache Tests wie den folgenden schreiben:
export class MyService {
helloWorld() {
return 'hello world';
}
}
//
import { MyService } from './my-service.ts';
test('hello world', () => {
const myService = new MyService();
expect(myService.helloWorld()).toBe('hello world');
});
Integration tests
Es ist nicht immer möglich, Unit-Tests zu schreiben, und es ist auch nicht immer der effizienteste Weg, um geschäftskritischen Code und Verhalten abzudecken. Besonders wenn Ihre Architektur sehr komplex ist, ist es von Vorteil, wenn Sie einfach End-to-End-Integrationstests durchführen können.
Wie Sie bereits im Kapitel Dependency Injection gelernt haben, ist der Dependency Injection Container das Herzstück von Deepkit. Hier werden alle Dienste aufgebaut und betrieben. Ihre Anwendung definiert Dienste (Provider), Controller, Listener und Importe. Bei Integrationstests wollen Sie nicht unbedingt alle Dienste in einem Testfall zur Verfügung haben, aber Sie wollen in der Regel eine abgespeckte Version der Anwendung zur Verfügung haben, um die kritischen Bereiche zu testen.
import { createTestingApp } from '@deepkit/framework';
import { http, HttpRequest } from '@deepkit/http';
test('http controller', async () => {
class MyController {
@http.GET()
hello(@http.query() text: string) {
return 'hello ' + text;
}
}
const testing = createTestingApp({ controllers: [MyController] });
await testing.startServer();
const response = await testing.request(HttpRequest.GET('/').query({text: 'world'}));
expect(response.getHeader('content-type')).toBe('text/plain; charset=utf-8');
expect(response.body.toString()).toBe('hello world');
});
import { createTestingApp } from '@deepkit/framework';
test('service', async () => {
class MyService {
helloWorld() {
return 'hello world';
}
}
const testing = createTestingApp({ providers: [MyService] });
//access the dependency injection container and instantiate MyService
const myService = testing.app.get(MyService);
expect(myService.helloWorld()).toBe('hello world');
});
Wenn Sie Ihre Anwendung in mehrere Module aufgeteilt haben, können Sie diese leichter testen. Nehmen wir zum Beispiel an, Sie haben ein AppCoreModul
erstellt und möchten einige Services testen.
class Config {
items: number = 10;
}
export class MyService {
constructor(protected items: Config['items']) {
}
doIt(): boolean {
//do something
return true;
}
}
export AppCoreModule = new AppModule({
config: config,
provides: [MyService]
}, 'core');
Sie verwenden Ihr Modul wie folgt:
import { AppCoreModule } from './app-core.ts';
new App({
imports: [new AppCoreModule]
}).run();
Und testen Sie es, ohne den gesamten Anwendungsserver zu booten.
import { createTestingApp } from '@deepkit/framework';
import { AppCoreModule, MyService } from './app-core.ts';
test('service simple', async () => {
const testing = createTestingApp({ imports: [new AppCoreModule] });
const myService = testing.app.get(MyService);
expect(myService.doIt()).toBe(true);
});
test('service simple big', async () => {
// you change configurations of your module for specific test scenarios
const testing = createTestingApp({
imports: [new AppCoreModule({items: 100})]
});
const myService = testing.app.get(MyService);
expect(myService.doIt()).toBe(true);
});