It starts quite simple. Once you have installed the nest CLI, you can issue a command like
nest new tutorial1
This will scaffold a new nestjs application in the directory „tutorial1“. Enter the directory. Then the app can directly be started using
npm run start:dev
If you visit then localhost:3000 you can see „Hello World!“ which is the standard output of the application. Nowadays everything needs to be stored in databases. So let’s install typeorm using sqlite.
npm install --save @nestjs/typeorm typeorm sqlite3
The next step seems to be the creation of a file called ormconfig.json in the applications root directory („tutorial1“ in our case). We’ll paste the following content:
[{
"name": "default",
"type": "sqlite",
"database": "./data/test.sqlite",
"entities": ["dist/**/*.entity{.ts,.js}"],
"synchronize": true
}]
This will tell typeorm to use an sqlite database located in directory data/ and apply its magic for all entity files we will create. This has to be known to the app module. We alter the app.module.ts file to look like:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
@Module({
imports: [TypeOrmModule.forRoot()],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
This will deal with the ormconfig.json file as stated here. If your command „npm run start:dev“ is still running you will now have a file called „data/test.sqlite“.
It is time now to decide what the application will be about. Of course this first tutorial will deal with photos and albums (as all nestjs examples seem to do (leaving alone cats and heros)).
nest generate module Photos
nest generate service Photos
nest generate controller Photos
So let’s create a photo as an entity description in „src/photos/photo.entity.ts“.
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Photo {
@PrimaryGeneratedColumn()
id: number;
@Column({ length: 500 })
name: string;
@Column('text')
description: string;
@Column()
filename: string;
@Column('int')
views: number;
@Column()
isPublished: boolean;
}
To fully unleash the automagical powers of the mixture of typeorm and nestjs we can make use of the crud controllers.
npm i --save @nestjsx/crud @nestjsx/crud-typeorm typeorm class-transformer class-validator
Since now I’m already lost a bit – what does that all provide ? – we will add the nestjs swagger integration to keep track of the routes all that magix will create for us.
npm install --save @nestjs/swagger swagger-ui-express
After this „main.ts“ needs to know about swagger.
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const options = new DocumentBuilder()
.setTitle('Photos example')
.setDescription('The photos API description')
.setVersion('1.0')
.addTag('photos')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
If you now visit http://localhost:3000/api all routes of your application will be shown. None, except „/“ that displays „Hello world!“ using the app controller.
I now tried to follow the steps of this documentation to add the crud wiring to the photo. It seems that swagger 4.0.9 is broken. So let’s revert (Hmmpf not commited yet :-/ )
npm uninstall --save @nestjs/swagger swagger-ui-express
and changing back main.ts to
import { NestFactory } from '@nestjs/core';
//import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// const options = new DocumentBuilder()
// .setTitle('Photos example')
// .setDescription('The photos API description')
// .setVersion('1.0')
// .addTag('photos')
// .build();
// const document = SwaggerModule.createDocument(app, options);
// SwaggerModule.setup('api', app, document);
await app.listen(3000);
}
bootstrap();
solves the problem temporarily.
The changes I have done so far are:
// photos.controller.ts
import { Controller } from '@nestjs/common';
import { Photo } from './photo.entity';
import { Crud } from '@nestjsx/crud';
import { PhotosService } from './photos.service';
@Crud({
model: {
type: Photo,
}
})
@Controller('photos')
export class PhotosController {
constructor(public service :PhotosService){}
}
// phots.module.ts
import { Module } from '@nestjs/common';
import { PhotosService } from './photos.service';
import { PhotosController } from './photos.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Photo } from './photo.entity';
@Module({
imports: [TypeOrmModule.forFeature([Photo])],
providers: [PhotosService],
controllers: [PhotosController]
})
export class PhotosModule {}
// photos.service.ts
import { Injectable } from '@nestjs/common';
import { TypeOrmCrudService } from '@nestjsx/crud-typeorm';
import { Photo } from './photo.entity';
import { InjectRepository } from '@nestjs/typeorm';
@Injectable()
export class PhotosService extends TypeOrmCrudService<Photo> {
constructor(@InjectRepository(Photo) repo) {
super(repo);
}
}
Since now I’m not able to show the routes that have been created using swagger, you can test that you will get an empty JSON response here.
Time to add some frontend. To make it simple we will just create a static handler and use parceljs to build the frontend. Because everything is bundled so nicely another package is needed.
npm install --save @nestjs/serve-static
We will follow these steps and add to „app.module.ts“:
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { PhotosModule } from './photos/photos.module';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
TypeOrmModule.forRoot(),
PhotosModule,
ServeStaticModule .forRoot({
rootPath: join(__dirname, '..', 'client-dist'),
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
No „client-dist“ directory exists so far. We can now install parcel
npm install -g parcel-bundler
Next I’ll create a folder structure
client/
├── index.html
├── main.css
└── main.js
Where index.html contains
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Photos</title>
<link rel="stylesheet" href="main.css">
</head>
<body>
<script src="main.js"></script>
</body>
</html>
Parcel can now be executed in another shell:
parcel client/index.html --out-dir client-dist
Now we delete the „Hello world!“ stuff from app.controller.ts
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
}
If you now visit http://localhost:3000 you will see an emtpy page with the title Photos. Let us add some content to „client/main.js“
import m from 'mithril';
m.mount(document.body, {
view: vnode => m('h1', 'Frontend')
});
Once we save the file, mithril will be installed as a dependency. What I personally don’t like at this point is that in package.json frontend and backend are mixed up and that we now need to add CORS to the backend to be able to use hot-reload in the frontend as well. We can do that by adding this line to „main.ts“.
app.enableCors({origin:'localhost:1234'});
A service in „main.js“ that fetches the photos rounds up our work until here.
import m from 'mithril';
const crudService = url => {
return {
list: cb => m.request({ url, method: 'GET', id: 0 }).then(cb)
};
};
const photoService = crudService('http://localhost:3000/photos');
let photos = [];
photoService.list(photos_ => photos = photos_)
m.mount(document.body, {
view: vnode => [
m('h1', 'Frontend'),
m('pre', JSON.stringify(photos, null, 2))
]
});
Now visit http://localhost:1234 to see the frontend, displaying an empty array.
This was the first part. It will be continued somewhen.