> ## Documentation Index
> Fetch the complete documentation index at: https://sonamu.cartanova.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# API Development

> Questions about API development and decorators

## @api Decorator

<AccordionGroup>
  <Accordion title="How do I use the @api decorator?">
    Attach the `@api` decorator to Model or Frame class methods to create REST API endpoints.

    **Basic usage:**

    ```typescript theme={null}
    class UserModelClass extends BaseModelClass {
      @api({ httpMethod: "GET" })
      async findById(id: number): Promise<User> {
        return this.findById(id);
      }

      @api({ httpMethod: "POST" })
      async createUser(params: UserSaveParams): Promise<User> {
        return this.save(params);
      }

      @api({ httpMethod: "PUT" })
      async updateUser(id: number, params: UserSaveParams): Promise<User> {
        return this.save({ id, ...params });
      }

      @api({ httpMethod: "DELETE" })
      async deleteUser(id: number): Promise<void> {
        await this.del(id);
      }
    }
    ```

    **Auto-generated endpoints:**

    ```
    GET    /user/findById?id=1
    POST   /user/createUser
    PUT    /user/updateUser
    DELETE /user/deleteUser?id=1
    ```
  </Accordion>

  <Accordion title="What are the options for @api decorator?">
    **httpMethod (required):**

    ```typescript theme={null}
    @api({ httpMethod: "GET" | "POST" | "PUT" | "DELETE" | "PATCH" })
    ```

    **guards (authentication/authorization):**

    ```typescript theme={null}
    // Authentication and authorization required
    @api({
      httpMethod: "POST",
      guards: ["user", "admin"]  // user login required, admin permission required
    })
    async adminOnly() { }

    // Public API (no authentication required)
    @api({
      httpMethod: "GET"
      // Omit guards to allow access without authentication
    })
    async publicApi() { }
    ```

    <Info>
      **Creating Public APIs**: Omit the `guards` option to create a public API accessible to anyone
      without authentication. Use this for endpoints that don't require login or permission checks.
    </Info>

    **path (custom path):**

    ```typescript theme={null}
    @api({
      httpMethod: "GET",
      path: "/custom/path"  // Use /custom/path instead of /user/method
    })
    async customPath() { }
    ```
  </Accordion>

  <Accordion title="How do I receive parameters in GET requests?">
    **Query parameters (single values):**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async getUser(id: number, includeProfile?: boolean): Promise<User> {
      // GET /user/getUser?id=1&includeProfile=true
    }
    ```

    **Query parameters (object):**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async listUsers(listParams: UserListParams): Promise<ListResult<User>> {
      // GET /user/listUsers?num=20&page=1&orderBy=id-desc
      return this.list("A", listParams);
    }
    ```
  </Accordion>

  <Accordion title="How do I receive body in POST requests?">
    **Single object:**

    ```typescript theme={null}
    @api({ httpMethod: "POST" })
    async createUser(params: UserSaveParams): Promise<User> {
      // POST /user/createUser
      // Body: { name: "John", email: "john@example.com" }
      return this.save(params);
    }
    ```

    **Multiple parameters:**

    ```typescript theme={null}
    @api({ httpMethod: "POST" })
    async updateProfile(
      userId: number,
      profile: ProfileSaveParams
    ): Promise<Profile> {
      // POST /user/updateProfile
      // Body: { userId: 1, profile: { bio: "..." } }
      return ProfileModel.save({ user_id: userId, ...profile });
    }
    ```
  </Accordion>

  <Accordion title="How do I create a file upload API?">
    The `@upload` decorator is used independently without `@api`.

    **Single file:**

    ```typescript theme={null}
    import { upload, Sonamu } from "sonamu";

    @upload()
    async uploadAvatar(): Promise<{ url: string; filename: string }> {
      // POST /user/uploadAvatar
      // Content-Type: multipart/form-data

      const { bufferedFiles } = Sonamu.getContext();
      const file = bufferedFiles?.[0]; // Use first file

      if (!file) {
        throw new Error("File is required");
      }

      const md5 = await file.md5();
      const key = `avatars/${md5}.${file.extname}`;
      const url = await file.saveToDisk("fs", key);

      return { url, filename: file.filename };
    }
    ```

    **Multiple files:**

    ```typescript theme={null}
    import { upload, Sonamu } from "sonamu";

    @upload()
    async uploadFiles(): Promise<{ files: Array<{ url: string; filename: string }> }> {
      // POST /user/uploadFiles
      // Content-Type: multipart/form-data

      const { bufferedFiles } = Sonamu.getContext();

      if (!bufferedFiles || bufferedFiles.length === 0) {
        throw new Error("At least one file is required");
      }

      const results = [];
      for (const file of bufferedFiles) {
        const md5 = await file.md5();
        const key = `files/${md5}.${file.extname}`;
        const url = await file.saveToDisk("fs", key);
        results.push({ url, filename: file.filename });
      }

      return { files: results };
    }
    ```
  </Accordion>

  <Accordion title="How do I return errors?">
    **BadRequestException (400):**

    ```typescript theme={null}
    import { BadRequestException } from "sonamu";

    @api({ httpMethod: "POST" })
    async createUser(params: UserSaveParams): Promise<User> {
      if (!params.email) {
        throw new BadRequestException("Email is required");
      }
      return this.save(params);
    }
    ```

    **UnauthorizedException (401):**

    ```typescript theme={null}
    import { UnauthorizedException } from "sonamu";

    @api({ httpMethod: "GET" })
    async getMyProfile(): Promise<User> {
      const userId = Sonamu.getContext().user?.id;
      if (!userId) {
        throw new UnauthorizedException("Login is required");
      }
      return this.findById(userId);
    }
    ```

    **Permission error (403):**

    ```typescript theme={null}
    import { UnauthorizedException } from "sonamu";

    @api({ httpMethod: "DELETE" })
    async deleteUser(id: number): Promise<void> {
      const currentUser = Sonamu.getContext().user;
      if (currentUser?.role !== "admin") {
        throw new UnauthorizedException("Admin permission required");
      }
      await this.del(id);
    }
    ```

    **NotFoundException (404):**

    ```typescript theme={null}
    import { NotFoundException } from "sonamu";

    @api({ httpMethod: "GET" })
    async getUserById(id: number): Promise<User> {
      const user = await this.findById(id);
      if (!user) {
        throw new NotFoundException("User not found");
      }
      return user;
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Context and Session

<AccordionGroup>
  <Accordion title="How do I get current logged-in user information?">
    ```typescript theme={null}
    import { Sonamu } from "sonamu";

    @api({ httpMethod: "GET" })
    async getMyProfile(): Promise<User> {
      const context = Sonamu.getContext();
      const userId = context.user?.id;

      if (!userId) {
        throw new UnauthorizedException("Login is required");
      }

      return this.findById(userId);
    }
    ```
  </Accordion>

  <Accordion title="What information is in Context?">
    ```typescript theme={null}
    const context = Sonamu.getContext();

    // Auth info (better-auth)
    context.user; // Logged-in user (null if unauthenticated)
    context.session; // Session (null if unauthenticated)
    context.user?.id; // User ID

    // Request info
    context.transport; // "http" | "ws"
    context.headers; // Request headers (IncomingHttpHeaders)
    context.headers["user-agent"]; // User-Agent
    context.locale; // Locale of the current request

    // Fastify objects (when transport === "http")
    context.request; // FastifyRequest (use context.request.ip for client IP)
    context.reply; // FastifyReply

    // Naite log store
    context.naiteStore;
    ```

    <Info>
      `userId`/`ip`/`userAgent` are not top-level properties on Context. Use
      `context.user?.id`, `context.request.ip`, and `context.headers["user-agent"]`
      respectively.
    </Info>
  </Accordion>

  <Accordion title="How do I use sessions?">
    **Session configuration:**

    ```typescript theme={null}
    // sonamu.config.ts
    export default {
      server: {
        session: {
          secret: process.env.SESSION_SECRET || "my-secret",
          cookie: {
            maxAge: 24 * 60 * 60 * 1000, // 24 hours
            secure: process.env.NODE_ENV === "production",
          },
        },
      },
    } satisfies SonamuConfig;
    ```

    **Save session:**

    ```typescript theme={null}
    @api({ httpMethod: "POST" })  // Omit guards = public API
    async login(email: string, password: string): Promise<User> {
      const user = await this.findOne("A", { email });

      if (!user || !await bcrypt.compare(password, user.password)) {
        throw new UnauthorizedException("Invalid email or password");
      }

      const context = Sonamu.getContext();
      context.session.userId = user.id;
      context.session.role = user.role;

      return user;
    }
    ```

    **Read session:**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async getMyInfo(): Promise<User> {
      const context = Sonamu.getContext();
      const userId = context.session.userId;

      if (!userId) {
        throw new UnauthorizedException("Login is required");
      }

      return this.findById(userId);
    }
    ```

    **Delete session:**

    ```typescript theme={null}
    @api({ httpMethod: "POST" })
    async logout(): Promise<{ success: boolean }> {
      const context = Sonamu.getContext();
      context.session.destroy();
      return { success: true };
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Authentication and Authorization

<AccordionGroup>
  <Accordion title="How do I control permissions with Guards?">
    **Guard configuration:**

    ```typescript theme={null}
    // sonamu.config.ts
    export default {
      server: {
        auth: {
          guards: {
            user: async (request) => {
              const userId = request.session.userId;
              if (!userId) return null;
              return UserModel.findById(userId);
            },
            admin: async (request) => {
              const user = request.session.user;
              if (!user || user.role !== "admin") return null;
              return user;
            },
          },
        },
      },
    } satisfies SonamuConfig;
    ```

    **Using Guards:**

    ```typescript theme={null}
    // user login required
    @api({ httpMethod: "GET", guards: ["user"] })
    async getMyProfile(): Promise<User> {
      const userId = Sonamu.getContext().user?.id;
      return this.findById(userId!);
    }

    // admin permission required
    @api({ httpMethod: "DELETE", guards: ["admin"] })
    async deleteUser(id: number): Promise<void> {
      await this.del(id);
    }

    // user or admin
    @api({ httpMethod: "GET", guards: ["user", "admin"] })
    async viewStats(): Promise<Stats> {
      // ...
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Response Format

<AccordionGroup>
  <Accordion title="How do I customize API responses?">
    **Default response:**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async getUser(id: number): Promise<User> {
      return this.findById(id);
    }

    // Response:
    // {
    //   "id": 1,
    //   "name": "John",
    //   "email": "john@example.com"
    // }
    ```

    **Custom response:**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async getUserWithStats(id: number): Promise<{
      user: User;
      stats: { postCount: number; commentCount: number };
    }> {
      const user = await this.findById(id);
      const postCount = await PostModel.count({ user_id: id });
      const commentCount = await CommentModel.count({ user_id: id });

      return {
        user,
        stats: { postCount, commentCount }
      };
    }
    ```

    **Setting headers:**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async downloadFile(id: number): Promise<Buffer> {
      const file = await FileModel.findById(id);

      const context = Sonamu.getContext();
      context.reply.header("Content-Type", "application/pdf");
      context.reply.header("Content-Disposition", `attachment; filename="${file.name}"`);

      return file.buffer;
    }
    ```
  </Accordion>

  <Accordion title="How do I return paginated responses?">
    **Using ListResult:**

    ```typescript theme={null}
    @api({ httpMethod: "GET" })
    async listUsers(listParams: UserListParams): Promise<ListResult<UserA>> {
      // ListResult = { rows: T[], total: number }
      return this.list("A", listParams);
    }

    // Response:
    // {
    //   "rows": [
    //     { "id": 1, "name": "John" },
    //     { "id": 2, "name": "Jane" }
    //   ],
    //   "total": 100
    // }
    ```
  </Accordion>
</AccordionGroup>

***

## Frontend Integration

<AccordionGroup>
  <Accordion title="How do I use auto-generated client code?">
    **Service files auto-generated:**

    When Model files change, `{entity}.service.ts` is auto-generated.

    ```typescript theme={null}
    // web/src/services/user/user.service.ts (auto-generated)
    export const UserService = {
      async findById(id: number): Promise<User> {
        const response = await axios.get("/user/findById", { params: { id } });
        return response.data;
      },

      async createUser(params: UserSaveParams): Promise<User> {
        const response = await axios.post("/user/createUser", params);
        return response.data;
      },
    };
    ```

    **Using in React:**

    ```typescript theme={null}
    import { UserService } from "@/services/user/user.service";

    function UserProfile({ userId }: { userId: number }) {
      const [user, setUser] = useState<User | null>(null);

      useEffect(() => {
        UserService.findById(userId).then(setUser);
      }, [userId]);

      return <div>{user?.name}</div>;
    }
    ```

    **Using TanStack Query hooks:**

    ```typescript theme={null}
    import { useUserFindById } from "@/services/user/user.service";

    function UserProfile({ userId }: { userId: number }) {
      const { data: user, error, isLoading } = useUserFindById({ id: userId });

      if (isLoading) return <div>Loading...</div>;
      if (error) return <div>Error: {error.message}</div>;

      return <div>{user?.name}</div>;
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Related Documentation

* [API Decorator](/en/api-development/api-decorator)
* [Context and Session](/en/api-development/context-session)
* [Authentication and Authorization](/en/api-development/authentication)
* [Error Handling](/en/api-development/error-handling)
* [Frontend Integration](/en/frontend-integration/using-services)
