# Controllers

## Introduction

The controller's purpose is to handle a set of requests related to the same resource.

The [Controller](https://github.com/puro-framework/puro-core/blob/master/src/controller.ts#L44) class supports four different methods, called "handlers", to implement the basic [CRUD operations](https://en.wikipedia.org/wiki/CRUD) on the resource:

| Controller Method | HTTP Method | HTTP Status Code |
| ----------------- | ----------- | ---------------- |
| create            | POST        | 201              |
| read              | GET         | 200              |
| update            | UPDATE      | 204              |
| remove            | DELETE      | 204              |

{% hint style="info" %}
The CRUD function **delete** has been mapped with the method **remove** in order to prevent conflicts with the reserved keyword "delete" in JavaScript/TypeScript.
{% endhint %}

The controller handler receives the request instance and returns a response content. Unless otherwise specified, the framework automatically sets the most appropriate [HTTP Status Code](https://en.wikipedia.org/wiki/List_of_HTTP_status_codes) for the request.

## Defining a controller

All the the controllers in a Puro application should be stored in the directory "controllers/".

The following example shows the simplest controller you can create:

{% code title="controllers/HelloController.ts" %}

```typescript
import { Controller, Request } from '@puro/core';

export class HelloController extends Controller {
  async read(request: Request) {
    return 'Hello World';
  }
}
```

{% endcode %}

It only handles GET requests to a resource and returns the string "Hello world" as response content.

```bash
curl -X GET http://127.0.0.1:8080/api/hello
```

```javascript
{
  "status": 200,
  "content": "Hello World"
}
```

```bash
curl -X DELETE http://127.0.0.1:8080/api/hello
```

```javascript
{
  "status": 405,
  "content": "Method Not Allowed"
}
```

## Defining a schema

A very interesting feature of Puro REST Framework is the built-in request validation. To achieve this result you need to define a "schema" for the controller handler that you want to validate.

For example, assuming we have a [BookController](https://github.com/puro-framework/puro-skeleton/blob/master/src/book/controllers/BookController.ts#L33) for the resource `/api/books/{:bookId}`, we could define a schema for the controller handler **update** as the following:

{% code title="controllers/BookController.schema.ts" %}

```typescript
import { Book } from '../entities/Book';

export const updateSchema = {
  bookId: {
    isEntityId: { type: Book, name: 'book' }
  },
  title: {
    isNotEmpty: true,
    isLength: { min: 3, max: 128 }
  },
  description: {
    isLength: { min: 0, max: 256 }
  }
};
```

{% endcode %}

The schema is a dictionary where the **key** is the name of the parameter that we want to validate and the **value** is an object containing the constraints we want to apply to that specific parameter.

{% hint style="info" %}
The parameters are searched in the URL parameters, the request body, and the URL query string, in that order.
{% endhint %}

{% hint style="info" %}
Puro REST Framework uses [validator.js](https://github.com/chriso/validator.js) to support the following constraints:

`isAfter`, `isAlpha`, `isAlphanumeric`, `isAscii`, `isBase64`, `isBefore`, `isBoolean`, `isByteLength`, `isCreditCard`, `isCurrency`, `isDecimal`, `isEmail`, `isEntityId`, `isFQDN`, `isFloat`, `isHash`, `isHexadecimal`, `isIP`, `isIPRange`, `isISO8601`, `isNotEmpty`, `isRFC3339`, `isISO31661Alpha2`, `isISO31661Alpha3`, `isIn`, `isInt`, `isJSON`, `isJWT`, `isLatLong`, `isLength`, `isLowercase`, `isMACAddress`, `isMimeType`, `isMobilePhone`, `isNumeric`, `isPort`, `isPostalCode`, `isURL`, `isUUID`, `isUppercase`, and `isWhitelisted`.
{% endhint %}

{% code title="controllers/BookController.ts" %}

```typescript
import { Controller, Schema, Request } from '@puro/core';

import { updateSchema } from './BookController.schema';

export class BookController extends Controller {
  @Schema(updateSchema)
  async update(request: Request) {
    const { book } = request.entities;
    const { title, description } = request.bucket;

    book.title = title;
    book.description = description;
    book.save();

    return 'The book has been updated.';
  }
}
```

{% endcode %}

## HTTP exceptions

TODO

| Exception Class               | HTTP Status Code | HTTP Response Message |
| ----------------------------- | ---------------- | --------------------- |
| HttpException                 | -                | -                     |
| BadRequestHttpException       | 400              | "Bad Request"         |
| AccessDeniedHttpException     | 403              | "Forbidden"           |
| NotFoundHttpException         | 404              | "Not Found"           |
| MethodNotAllowedHttpException | 405              | "Method Not Allowed"  |
| InvalidParameterHttpException | 422              | "Invalid Parameter"   |
