Using Angular and Spring boot design patterns, Litstack is a Typescript REST framework that Angular/Spring Boot engineers already know how to use, keeping their code organized and concise, and pushing off the express wiring to the library.
Follow the directions in the litstack seed to get started right away.
Create a new project and install the Litstack core library.
> npm install @litstack/core --save
Make sure experimental decorators are on in your tsconfig.json at the root level:
{
"compilerOptions": {
"experimentalDecorators": true
}
}
For more on building, see the minimum configuration section below.
Bootstrap app.module at the index.ts level:
// in index.ts
import { LitCompiler } from '@litstack/core/dist/compiler';
import { AppModule } from './modules/app.module';
LitCompiler.bootstrap(AppModule);
Modules can export component routes and package other modules.
This module will export app.component's routes.
// in app.module.ts
import { LitModule } from '@litstack/core';
import { AppComponent } from './app.component';
@LitModule({
exports: [
AppComponent
]
})
export class AppModule {
}
Modules can also import other modules with components:
import { LitModule } from '@litstack/core';
import { ItemsModule } from './modules/items/items.module';
import { OrdersModule } from './modules/orders/orders.module';
import { PeopleModule } from './modules/people/people.module';
import { VersionComponent } from './components/version/version.component';
@LitModule({
path: 'api', // will add all imported routes at '/api/..'
imports: [
ItemsModule,
PeopleModule,
OrdersModule
],
exports: [
VersionComponent
]
})
export class AppModule {
}
Components register route listeners:
// in app.component.ts
import { LitComponent } from '@litstack/core';
import { HttpResponse } from '@litstack/core/dist/http';
import { GetMapping } from '@litstack/core/dist/http/mappings';
@LitComponent()
export class AppComponent {
private message = 'Hello World!';
@GetMapping() // defaults to '/'
onHello(res: HttpResponse): void {
res.success({
message: this.message
});
}
}
Specify params in the path:
import { LitComponent } from '@litstack/core';
import { HttpRequest, HttpResponse } from '@litstack/core/dist/http';
import { GetMapping } from '@litstack/core/dist/http/mappings';
@LitComponent()
export class ItemsComponent {
@GetMapping({
path: ':id'
})
getItem(req: HttpRequest, res: HttpResponse): void {
res.success({
id: req.params.id
});
}
}
We can keep our route functionality isolated by using the "next" param:
import { LitComponent } from '@litstack/core';
import { HttpRequest, HttpResponse, HttpNext } from '@litstack/core/dist/http';
import { PutMapping } from '@litstack/core/dist/http/mappings';
@LitComponent()
export class ItemsComponent {
// NOTE: The order matters here:
@PutMapping({
path: ':id'
})
updateItem(req: HttpRequest, res: HttpResponse, next: HttpNext) {
if(req.param.id === 'some-id') {
// do some update
res.success({ id: 'some-id' });
return;
}
next();
}
// same route as above, will run only if "next" is called
@PutMapping({
path: ':id'
})
updateItemErr(res: HttpResponse) {
res.error(404);
}
}
Services are a great place for business logic:
// ./services/items.service
import { LitService } from '@litstack/core';
@LitService()
export class ItemsService {
get description(): string {
return 'This is an item description';
}
}
And then in our component:
import { LitComponent } from '@litstack/core';
import { HttpResponse } from '@litstack/core/dist/http';
import { GetMapping } from '@litstack/core/dist/http/mappings';
import { ItemsService } from './services/items.service';
@LitComponent()
export class ItemsComponent {
constructor(private itemsService: ItemsService) {}
@GetMapping()
getItems(res: HttpResponse) {
res.success({
description: this.itemsService.description
});
}
}
Test components using supertest methods and the Litstack TestBed:
import { TestBed, LitComponentTest } from '@litstack/core/dist/testing';
import { AppComponent } from './app.component';
describe('AppComponent', () => {
let component: LitComponentTest;
beforeEach(() => {
component = TestBed.start(AppComponent);
});
afterEach(() => {
TestBed.stop();
});
it('should return a welcome message', (done) => {
component
.get('/')
.expect(200)
.expect((res) => {
expect(res.body.message).to.equal('Hello World!');
})
.end((err, res) => {
if (err) return done(err);
done();
});
});
});
The build process can be customized to the needs of the project, but a minimum configuration could look like this:
> mkdir my-project
> cd my-project
> npm init -y
> npm install @litstack/core --save
> npm install typescript -D
> npm install ts-node -D
Change the following in package.json:
{
"main": "dist/index.js",
"scripts": {
"start": "tsc && node dist/index.js"
}
}
A tsconfig.json could look like this:
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"outDir": "dist",
"lib": [
"es7"
],
"experimentalDecorators": true,
"emitDecoratorMetadata": true
},
"include": [
"**/*.ts"
]
}
Now, run the app:
> npm start
Have fun!