First app
In this guide you build a small API that lists books. It shows the three pieces every viga app is made of: services, controllers and modules. Before you start, follow the installation steps.
Project structure
Section titled “Project structure”When you’re done, the project will have these files:
src/ books/ books.service.ts books.controller.ts books.module.ts app.module.ts main.tsEach feature lives in its own folder with its module. app.module.ts brings them together and main.ts starts the app.
The service
Section titled “The service”A service holds the app’s logic. Here it returns a fixed list of books. In a real app it would query the database.
import { Injectable } from 'viga';
export interface Book { id: number; title: string;}
@Injectable()export class BooksService { private readonly books: Book[] = [ { id: 1, title: 'Dune' }, { id: 2, title: 'Foundation' }, ];
findAll() { return this.books; }}@Injectable() lets viga create the service and hand it to whoever asks for it.
The controller
Section titled “The controller”A controller receives HTTP requests and responds. It holds no logic: it asks the service for it.
import { Controller, Get, Public, inject } from 'viga';import { BooksService } from './books.service.ts';
@Controller('/books')export class BooksController { private readonly books = inject(BooksService);
@Public() @Get() list() { return this.books.findAll(); }}@Controller('/books')sets the prefix for all the controller’s routes.@Get()responds toGET /books. Whatever the method returns is sent as JSON, and if it’s a promise, viga waits for it to resolve.inject(BooksService)asks for the service. viga creates it once and shares it.@Public()opens the route. Without it, the route would be private.
The module
Section titled “The module”A module groups the controllers and services of a feature.
import { Module } from 'viga';import { BooksController } from './books.controller.ts';import { BooksService } from './books.service.ts';
@Module({ controllers: [BooksController], providers: [BooksService],})export class BooksModule {}Services go in providers. Only this module’s controllers and services can ask for them, unless the module exports them.
The root module
Section titled “The root module”The root module brings the app’s modules together:
import { Module } from 'viga';import { BooksModule } from './books/books.module.ts';
@Module({ imports: [BooksModule] })export class AppModule {}Start the app
Section titled “Start the app”import { createApp } from 'viga';import { AppModule } from './app.module.ts';
const app = createApp({ rootModule: AppModule, globalPrefix: '/api' }).enableShutdownHooks();
await app.listen();globalPrefixputs/apiin front of every route.enableShutdownHooks()shuts the app down in order when the process stops, for example withCtrl+C.listen()starts listening on port 3000. The port is optional:listen(8080)uses a different one. viga doesn’t read thePORTenvironment variable on its own; if you use it, pass it yourself:app.listen(Number(process.env.PORT ?? 3000)).
Start it in development mode:
pnpm devTry the API
Section titled “Try the API”curl http://localhost:3000/api/books[{ "id": 1, "title": "Dune" }, { "id": 2, "title": "Foundation" }]Routes are private by default
Section titled “Routes are private by default”Remove @Public() from the controller and try again. The route responds 401:
{ "statusCode": 401, "code": "errors.unauthorized", "message": "You must sign in to continue", "error": "Unauthorized"}Also, on startup, the app warns you how many private routes have no way to authenticate:
[App] No auth guard is configured, so the 1 private routes will respond 401; add app.useAuthGuard() or mark public routes with @Public()In viga, a route is opened on purpose, never by oversight. For routes that require a session, you set up your own authentication with app.useAuthGuard(), as you’ll see in the core section.
Next steps
Section titled “Next steps”You now have an app with a module, a controller and a service. The next step is to move data access into a repository, so you can change where the data comes from without touching the service and replace it in tests. After that you can validate incoming data, organize more modules and connect a database, which is what the following sections cover.