# Simple API

## Setup Your Project

First, create a new directory for your project and initialize it with soap cli:

```bash
mkdir bookstore-api
cd bookstore-api
soap new project -n "Bookstore" -l "typescript" -s "src" -f "express" -d "mongo" -i "inversify"
```

After creating your project, your file structure (assuming default configuration settings) should look like this:

{% tabs %}
{% tab title="TypeScript" %}

```bash
.soap/
  - cli.config.js
  - plugin-map.json
  - plugin.config.json
  - project.config.json
  - texts.json
node_modules/
src/
  - index.ts
  - dependencies.ts
  - routes.ts
package.json
package-lock.json
tsconfig.json
```

{% endtab %}
{% endtabs %}

## Create components

Ok, now let's look at the source of our API, you can create each component one by one using the `soap new <component_name>` command, but we will use a JSON file first. Let's assume that we have planned everything before we start coding.

<details>

<summary>books-api.json</summary>

```json
{
  "controllers":[
    {
      "name": "Books",
      "endpoint": "books",
      "handlers": [
        {
          "name": "getAllBooks",
          "output": "Array<Entity<Book>>"
        },
        {
          "name": "getBook",
          "input": { "title":"string" }
          "output": "Entity<Book>"
        },
        {
          "name": "addBook",
          "output": "boolean"
        },
        {
          "name": "removeBook",
          "output": "boolean"
        }
      ]
    }
  ],
  "routes": [
    {
      "name": "GetAllBooks",
      "endpoint": "books",
      "handler": {
        "controller": "books",
        "name": "getAllBooks"
      },
      "request": {
        "method": "GET",
        "path": "/list"
      },
      "response": {
        "200": "Array<RouteModel<Book>>",
        "500": "Error",
        "404": { "message": "string", "error_code": "number" }
      }
    }
  ],
  "entities": [
    {
      "name": "Book",
      "endpoint": "books",
      "props": [
        "title:string",
        "author:string",
        "publishedYear: number"
      ]
    }
  ],
  "models": [
    {
      "name": "Book",
      "endpoint": "books",
      "types": ["route_model"],
      "props": [
        {
          "name": "title",
          "type": "string"
        },
        {
          "name": "author",
          "type": "string"
        },
        {
          "name": "published_year",
          "type": "number"
        }
      ]
    }
  ],
  "use_cases": [
    {
      "name": "GetAllBooks",
      "endpoint": "books"
      "output": "Array<Entity<Book>"
    }
  ],
  "repositories": [
    {
      "name": "Book",
      "endpoint": "books",
      "contexts": ["mongo"]
    }
  ],
  "collections": [
    {
      "name": "Book",
      "endpoint": "books",
      "storages": ["mongo"],
      "model": "Book",
      "table": "book.collection"
    }
  ]
}
```

</details>

With the JSON file ready, we can use the command `soap new --json -w` to create all the elements included in this JSON. After this operation, if everything went smoothly, we should have the following file structure.

<details>

<summary>File structure</summary>

```
.soap/
  - cli.config.js
  - plugin-map.json
  - plugin.config.json
  - project.config.json
  - texts.json
node_modules/
src/
  - endpoints/
    - books/
      - data/
        - dtos/
          - book.dto.ts
          - index.ts
        - collections/
          - book.mongo.collection.ts
          - index.ts
        - mappers/
          - book.mongo.mapper.ts
          - index.ts
      - domain/
        - books.controller.ts
        - use-cases/
          - get-all-books.use-case.ts
          - index.ts
        - entities/
          - book.ts
          - index.ts
        - repositories/
          - book.repository.ts
          - index.ts
      - routes/
        - get-all-books.route.ts
        - get-all-books.route-io.ts
        - get-all-books.route-model.ts
        - index.ts
  - index.ts
  - dependencies.ts
  - routes.ts
package.json
package-lock.json
tsconfig.json
```

</details>

This is what the file structure looks like after creating the components. Remember, you can customize this structure by making changes to the `.soap/plugin.config.json` file.&#x20;

After generating the files, your task will be to add logic in the controller, you will need to decide which and when use cases to invoke. In the use cases themselves, you will need to implement logic for retrieving information about all books. Since you are using a MongoDB database, you must also define how you will map values from documents to entities and vice versa. For this purpose, you need to edit the mapper file. Finally, don't forget about mapping the result obtained from the controller method and parsing it into a specific request response.

## Content of the generated files

The content of the files after generation should look as follows, leaving you room for implementation. Since we didn't use the `--skip-tests` option, remember to fill out the test templates. By default, they are placed in the `__tests__` directories.

### Root layer

{% tabs %}
{% tab title="index.ts" %}

```typescript
import { Container } from "inversify";
import bodyParser from "body-parser";
import cors from "cors";
import express from "express";
import { Routes } from "./routes";
import { Dependencies } from "./dependencies";
export * from "./routes";
export * from "./dependencies";

const start = async () => {
  const app = express();
  app.use(
    cors({
      origin: "*",
    })
  );
  app.use(bodyParser.json());
  const routes = new Routes(app);
  const container = new Container();
  const dependencies = new Dependencies();

  dependencies.configure(container);
  routes.configure(container);

  app.listen(process.env.PORT, () => {
    console.log(`Server is running at http://localhost:${process.env.PORT}`);
  });
};

start();
```

{% endtab %}

{% tab title="router.ts" %}

```typescript
import { Container } from "inversify";
import * as Soap from "@soapjs/soap";
import { GetAllBooksRoute } from "./endpoints/books/routes/get-all-books.route";
import { BooksController } from "./endpoints/books/domain/controllers/books.controller";

export class Router extends Soap.Router {
  public configure(container: Container) {
    const booksController =
      container.get<BooksController>(BooksController.Token);
    this.mount(
      GetAllBooksRoute.create(booksController.getAllBooks.bind(booksController))
    );
  }
}
```

{% endtab %}

{% tab title="dependencies.ts" %}

```typescript
import { Container } from "inversify";
import { RepositoryImpl, ApiConfig } from "@soapjs/soap";
import { MongoSource } from "@soapjs/soap-node-mongo";
import { BooksController } from "./endpoints/books/domain/controllers";
import { GetAllBooksUseCase } from "./endpoints/books/domain/use-cases";
import { BookRepository } from "./endpoints/books/domain/repositories";
import { BookMongoMapper } from "./endpoints/books/data/mappers";
import { BookMongoCollection } from "./endpoints/books/data/collections";

export class Dependencies {
  public async configure(container: Container, config: ApiConfig) {
    const mongoSource = MongoSource.create(config);
    await mongoSource.connect();
    const context = {
      collection: new BookMongoCollection(mongoSource),
      mapper: new BookMongoMapper(),
      queries: new MongoQueries()
    }
    const impl = new RepositoryImpl(context);
    container
      .bind<BookRepository>(BookRepository.TOKEN)
      .toConstantValue(impl);
    container
      .bind<GetAllBooksUseCase>(GetAllBooksUseCase.TOKEN)
      .to(GetAllBooksUseCase);
    container.bind<BooksController>(BooksController.TOKEN).to(BooksController);
  }
}
```

{% endtab %}
{% endtabs %}

### Routes

{% tabs %}
{% tab title="get-all-books.route.ts" %}

```typescript
import { RouteHandler, GetRoute } from "@soapjs/soap";
import { GetAllBooksRouteIO } from "./get-all-books.route-io";
import { BooksController } from "../domain/controllers";

export class GetAllBooksRoute extends GetRoute {
  public static create(handler: RouteHandler) {
    return new GetAllBooksRoute("/books/list", handler);
  }

  private constructor(handler: RouteHandler) {
    super(handler);
  }
}
```

{% endtab %}

{% tab title="get-all-books.route-io.ts" %}

```typescript
import { RouteIO, Response, Request } from "@soapjs/soap";
import { Book } from "../domain/entities";
import { BookRouteModel } from "./get-all-books.route-model.ts";

export class GetAllBooksRouteIO implements RouteIO {
  public toResponse(output: Result<Book[]>): Response<BookRouteModel[]> {
    if (output.isFailure) {
      return {
        body: output.failure.error.message,
        status: 500
      }
    }
    // TODO: convert result's content to the response body ...
    return result.content;
  }
  public fromRequest(request: Request) {}
}
```

{% endtab %}

{% tab title="get-all-books.route-model.ts" %}

```typescript
export type BookRouteModel = {
  title: string;
  author: string;
  published_year: number;
}
```

{% endtab %}
{% endtabs %}

### Domain layer

{% tabs %}
{% tab title="controller.ts" %}

```typescript
import { injectable } from "inversify";
import { Result } from "@soapjs/soap";
import {
  Book,
  GetBookInput,
  AddBookInput,
  RemoveBookInput
} from "./entitites";

@injectable()
export class BooksController {
  public static Token = "BooksController";

  public async getAllBooks(): Promise<Result<Book[]>> {
    // TODO: implement method
  }
  public async getBook(input: GetBookInput): Promise<Result<Book[]>> {
    // TODO: implement method
  }
  public async addBook(input: AddBookInput): Promise<Result<boolean>> {
    // TODO: implement method
  }
  public async removeBook(input: RemoveBookInput): Promise<Result<boolean>> {
    // TODO: implement method
  }
}
```

{% endtab %}

{% tab title="get-all-books.use-case.ts" %}

```typescript
import { injectable } from "inversify";
import { Result } from "@soapjs/soap";
import { Book } from "./entitites";

@injectable()
export class GetAllBooksUseCase implements UseCase<Book[]>{
  public static Token = "GetAllBooksUseCase";
  
  public async execute(): Promise<Result<Book[]>> {
    // TODO: implement method
  }
}
```

{% endtab %}

{% tab title="book.ts" %}

```typescript
export class Book {
  constructor(
    public readonly title: string,
    public readonly author: string,
    public readonly publishedYear: number,
  ){}
}
```

{% endtab %}

{% tab title="book.repository.ts" %}

```typescript
import { injectable } from "inversify";
import { Book } from "./entitites";

@injectable()
export class BookRepository implements Repository<Book>{
  public static Token = "BookRepository";
}
```

{% endtab %}
{% endtabs %}

### Data layer

{% tabs %}
{% tab title="book.mongo.collection.ts" %}

```typescript
import { MongoSource, MongoCollection } from "@soapjs/soap-node-mongo";
import { BookMongoModel } from "../dtos";

export class BookMongoCollection extends MongoCollection<BookMongoModel> {
  constructor(source: MongoSource) {
    super(source, 'book.collection');
  }
}
```

{% endtab %}

{% tab title="book.mongo.mapper.ts" %}

```typescript
import { Mapper } from "@soapjs/soap";
import { BookMongoModel } from "../dtos";
import { Book } from "../../domain/entities";

export class BookMongoMapper implements Mapper<Book, BookMongoModel> {
  public toEntity(model:BookMongoModel): Book {
    // TODO: implement method
  }
  
  public fromEntity(entity:Book): BookMongoModel {
    // TODO: implement method
  }
}
```

{% endtab %}

{% tab title="book.dto.ts" %}

```typescript
import { ObjectId } from 'mongodb';

export type BookMongoModel = {
  id: ObjectId;
  title: string;
  author: string;
  published_year: number;
}
```

{% endtab %}
{% endtabs %}

This roughly outlines the generated code. Your task now will be to fill in the code segments to achieve the desired outcome when sending requests to the endpoint. Let's start with mapping the request data to the input for the controller and the controller's output to the response. All this needs to be done in the `GetAllBooksRouteIO` class. You can, of course, add your mapper, but essentially, `RouteIO` serves as a mapper itself.&#x20;

In our example, the request does not contain any parameters, so there is no need to implement `fromRequest`. However, it's different with `toResponse`; the `Controller` always returns a `Result` object with the content type you choose, here an array of `Book` entities. We now need to convert this array into an array of `BookRouteModel` models or return an error if the result contains a failure.

```typescript
import { RouteIO, Response, Request } from "@soapjs/soap";
import { Book } from "../domain/entities";
import { BookRouteModel } from "./get-all-books.route-model.ts";

export class GetAllBooksRouteIO implements RouteIO {
  public toResponse(output: Result<Book[]>): Response<BookRouteModel[]> {
    if (output.isFailure) {
      return {
        body: output.failure.error.message,
        status: 500
      };
    }
    
    return {
      status: 200,
      body: result.content
        .map(entity => ({
          author: entity.author,
          title: entity.author,
          published_year: entity.publishedYear
        }))
    };
  }
}
```

Great, now we need to tell the controller what to do when the `getAllBooks` method is called. Our example is very simple and doesn't require any logic in the controller. It's your code, and you can decide whether to directly call the repository here, but for more complex scenarios, it's better to limit the controller to calling the appropriate use cases. This way, you don't clutter the controller with "helpers" or dedicated private methods (which could be untestable). The controller, by nature, takes a command and passes it on. That's what we'll do in our case, even though it's simple.

```typescript
...
import { GetAllBooksUseCase } from "./use-ceses";
import { Book } from "./entitites";

@injectable()
export class BooksController {
  public static Token = "BooksController";
  constructor(
    @inject(GetAllBooksUseCase.Token) private getAllBooksUseCase:GetAllBooksUseCase;
  ){}

  public async getAllBooks(): Promise<Result<Book[]>> {
    return this.getAllBooksUseCase.execute();
  }
  ...
}
```

And now, the most important part, though in our case, it's equally easy. We need to apply the repository in the use case and call the data fetching for the books.

```typescript
import { injectable } from "inversify";
import { Result } from "@soapjs/soap";
import { Book } from "./entitites";

@injectable()
export class GetAllBooksUseCase implements UseCase<Book[]>{
  public static Token = "GetAllBooksUseCase";
  
  constructor(
    @inject(BookRepository.Token) private bookRepository: BookRepository
  ){}
  public async execute(): Promise<Result<Book[]>> {
    return this.bookRepository.find();
  }
}
```

And that's all, a simple example but it gives a general idea of what you can do. Remember to complete the unit tests, create the database, and run the application.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.soapjs.com/examples/simple-api.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
