Desenvolver APIs de qualidade exige mais do que conhecer uma linguagem. Vamos explorar boas práticas usando Golang (backend) e TypeScript (cliente).
Por Que Golang para APIs?
Go se destaca para APIs por diversos motivos:
- **Performance:** Compilado, concorrente, rápido
- **Simplicidade:** Sintaxe limpa, fácil de manter
- **Stdlib robusta:** HTTP server, JSON, crypto nativos
- **Deploy simples:** Binário único, sem dependências
Estrutura do Projeto
Backend (Golang)
```
api/
├── cmd/
│ └── server/
│ └── main.go
├── internal/
│ ├── handlers/
│ ├── models/
│ ├── services/
│ └── middleware/
├── pkg/
│ └── utils/
└── go.mod
```
Cliente (TypeScript)
```
client/
├── src/
│ ├── api/
│ │ ├── client.ts
│ │ └── types.ts
│ └── index.ts
├── package.json
└── tsconfig.json
```
Boas Práticas: Backend (Golang)
1. Estrutura de Handlers
❌ Ruim:
```go
func GetUser(w http.ResponseWriter, r *http.Request) {
// Tudo misturado
}
```
✅ Bom:
```go
type UserHandler struct {
service UserService
}
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
// Handler limpo, delega ao service
}
```
2. Validação de Input
❌ Ruim:
```go
user := User{}
json.Unmarshal(body, &user)
// Usar sem validar
```
✅ Bom:
```go
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=2"`
}
func (h *UserHandler) Create(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respondError(w, http.StatusBadRequest, "Invalid JSON")
return
}
if err := validate.Struct(req); err != nil {
respondError(w, http.StatusBadRequest, err.Error())
return
}
// Prosseguir com dados válidos
}
```
3. Error Handling Consistente
✅ Crie tipos de erro padronizados:
```go
type APIError struct {
Code int `json:"code"`
Message string `json:"message"`
Details string `json:"details,omitempty"`
}
func respondError(w http.ResponseWriter, code int, message string) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
json.NewEncoder(w).Encode(APIError{
Code: code,
Message: message,
})
}
```
4. Middleware para Concerns Transversais
```go
func LoggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
next.ServeHTTP(w, r)
log.Printf(
"%s %s %s",
r.Method,
r.RequestURI,
time.Since(start),
)
})
}
func CORSMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
if r.Method == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
```
5. Context para Timeout e Cancelamento
```go
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
user, err := h.service.GetUser(ctx, userID)
if err != nil {
if err == context.DeadlineExceeded {
respondError(w, http.StatusGatewayTimeout, "Request timeout")
return
}
respondError(w, http.StatusInternalServerError, err.Error())
return
}
respondJSON(w, http.StatusOK, user)
}
```
Boas Práticas: Cliente (TypeScript)
1. Cliente Tipado
```typescript
// types.ts
export interface User {
id: string;
email: string;
name: string;
createdAt: string;
}
export interface CreateUserRequest {
email: string;
name: string;
}
export interface APIError {
code: number;
message: string;
details?: string;
}
```
2. Cliente HTTP com Interceptors
```typescript
// client.ts
class APIClient {
private baseURL: string;
private token?: string;
constructor(baseURL: string) {
this.baseURL = baseURL;
}
setToken(token: string) {
this.token = token;
}
private async request<T>(
path: string,
options: RequestInit = {}
): Promise<T> {
const url = `${this.baseURL}${path}`;
const headers = new Headers(options.headers);
headers.set('Content-Type', 'application/json');
if (this.token) {
headers.set('Authorization', `Bearer ${this.token}`);
}
const response = await fetch(url, {
...options,
headers,
});
if (!response.ok) {
const error: APIError = await response.json();
throw new Error(error.message);
}
return response.json();
}
async getUser(id: string): Promise<User> {
return this.request<User>(`/users/${id}`);
}
async createUser(data: CreateUserRequest): Promise<User> {
return this.request<User>('/users', {
method: 'POST',
body: JSON.stringify(data),
});
}
}
export const apiClient = new APIClient('https://api.example.com');
```
3. Retry Logic
```typescript
async function withRetry<T>(
fn: () => Promise<T>,
maxRetries = 3,
delay = 1000
): Promise<T> {
let lastError: Error;
for (let i = 0; i < maxRetries; i++) {
try {
return await fn();
} catch (error) {
lastError = error as Error;
if (i < maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delay * (i + 1)));
}
}
}
throw lastError!;
}
// Uso
const user = await withRetry(() => apiClient.getUser('123'));
```
Versionamento de API
Estratégias:
1. Via URL (Recomendado)
```
/v1/users
/v2/users
```
2. Via Header
```
Accept: application/vnd.api.v1+json
```
3. Via Query Parameter (Menos comum)
```
/users?version=1
```
Documentação
Swagger/OpenAPI
Use anotações para gerar docs automaticamente:
```go
// @Summary Get user by ID
// @Description Get details of a user
// @Tags users
// @Accept json
// @Produce json
// @Param id path string true "User ID"
// @Success 200 {object} User
// @Failure 404 {object} APIError
// @Router /users/{id} [get]
func (h *UserHandler) GetUser(w http.ResponseWriter, r *http.Request) {
// ...
}
```
Testes
Backend (Go)
```go
func TestGetUser(t *testing.T) {
// Setup
mockService := &MockUserService{}
handler := &UserHandler{service: mockService}
// Request
req := httptest.NewRequest("GET", "/users/123", nil)
w := httptest.NewRecorder()
// Execute
handler.GetUser(w, req)
// Assert
assert.Equal(t, http.StatusOK, w.Code)
}
```
Cliente (TypeScript)
```typescript
import { describe, it, expect, vi } from 'vitest';
describe('APIClient', () => {
it('should get user', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({ id: '123', name: 'Test' }),
});
const user = await apiClient.getUser('123');
expect(user.id).toBe('123');
});
});
```
Conclusão
APIs bem projetadas são:
- **Consistentes:** Padrões claros em toda API
- **Documentadas:** Fáceis de entender e usar
- **Testadas:** Cobertura de testes adequada
- **Versionadas:** Evoluem sem quebrar clientes
- **Monitoradas:** Métricas e logs para troubleshooting
Próximos passos:
1. Escolha seu framework (Gin, Echo, Chi para Go)
2. Configure estrutura de projeto
3. Implemente middleware essencial
4. Escreva testes desde o início
5. Configure CI/CD
Precisa de ajuda com sua API? [Entre em contato](/contato).