Skip to content

Controllers

Overview

Controllers are designed in the Sodacore framework as being the location of your business logic, and with the help of Providers you can keep your code clean and easy to maintain and separate the parts of your application into small pieces. In a Sodacore application, a controller is a handler for all services, so http requests or websocket messages, etc will all come through to a controller, your logic starts there, and then you can call providers and other imported code to actually action the request.

Create your first controller

Before we start, please note that the @sodacore/core package out of the box comes with no services therefore, you need to install the plugin for the service you want.

We are going to assume for the rest of this guide that you have the HTTP plugin installed and configured, please see @sodacore/http for more information.

To create a HTTP controller, we suggest the following structure:

plaintext
- tsconfig.json
- package.json
+ src/
	- main.ts
	+ controller/
		- example.ts

The contents of example.ts should be:

typescript
import { Controller, Get } from '@sodacore/http';

@Controller('/example')
export class ExampleController {

	@Get()
	public async index() {
		return 'Hello, World!';
	}
}

The above does the following:

  1. We create a new class called ExampleController.
  2. We use the @Controller decorator to tell the framework that this is a controller and pass it a base path (the given path is optional).
  3. We use the @Get decorator to tell the framework that this method should be called when a GET request is made to /example and all method decorators take a path if you want more control.
  4. When the method is called, we shall return a string saying Hello, World!.

You can learn more about the HTTP plugin's method decorators here: @sodacore/http: decorators.

Try it out! bun run ./src/main.ts --target=bun and then go to http://localhost:8080/example and you should see Hello, World!.

Controller responses

Before we go any further, let's explain how the framework handles HTTP responses; it's fairly simple. We try to do some automation behind the scenes for you, but you can always override it.

By default the following is done:

  1. If the value is a Response instance, we just return that.
  2. If the value is an error, we will send back a 500.
  3. If the value is null, we return a 404.
  4. If the value is a string or number, we return as a 200.
  5. If the value is an object, we return as a 200 and stringify the returned data with application/json headers.
  6. If the value is undefined, we return a 204.

As noted in #1, if you want to customise the responses, you can simply rewrite the above like so:

This uses Bun's built-in Response object, you do not need to import anything as it's part of the global scope, you can read more here: Bun API: HTTP.

typescript
import { Controller, Get } from '@sodacore/http';

@Controller('/example')
export class ExampleController {

	@Get()
	public async index() {
		return new Response('Hello, World!', { status: 201 });
	}
}

Dynamic routes

As part of the RESTful API design, you will probably want to have a dynamic route that has the resource ID in the URL, and you may want to get this from the URL in your own code, let's create a simple example of that.

typescript
import { Controller, Get, Param } from '@sodacore/http';

@Controller('/example')
export class ExampleController {
	private todos = [
		{ id: 'b0dcd7ef', name: 'Feed the cats.', completed: true },
		{ id: '00b05071', name: 'Take out the trash.', completed: false },
		{ id: '5e5a9238', name: 'Wash the dishes.', completed: false },
	];

	@Get()
	public async index() {
		return this.todos;
	}

	@Get('/:id')
	public async get(@Param('id') id: string) {
		return this.todos.find(todo => todo.id === id);
	}
}

As you can see, we have parameter decorators as well, to allow you to retrieve data from the URL, and have it added to the method call.

You can learn more about the decorators here: @sodacore/http: decorators.

Full Example

A full todo example is below.

typescript
import { Controller, Get, Param, Post, Body, Delete } from '@sodacore/http';

@Controller('/todo')
export class TodoController {
	private todos = [
		{ id: 'b0dcd7ef', name: 'Feed the cats.', completed: true },
		{ id: '00b05071', name: 'Take out the trash.', completed: false },
		{ id: '5e5a9238', name: 'Wash the dishes.', completed: false },
	];

	@Get()
	public async index() {
		return this.todos;
	}

	@Get('/:id')
	public async get(@Param('id') id: string) {
		return this.todos.find(todo => todo.id === id);
	}

	@Post()
	public async create(@Body() body: { name: string }) {
		const id = Math.random().toString(36).substring(5);
		this.todos.push({ id, name: body.name, completed: false });
		return this.todos;
	}

	@Delete('/:id')
	public async delete(@Param('id') id: string) {
		this.todos = this.todos.filter(todo => todo.id !== id);
		return this.todos;
	}
}

Released under the Apache-2.0 License.