---
title: "Zig HTTP POST: enviar JSON e formulários com std.http.Client"
url: "https://ziglang.com.br/receitas/zig-http-post-request/"
markdown_url: "https://ziglang.com.br/receitas/zig-http-post-request.MD"
description: "Receita prática de Zig HTTP POST com std.http.Client: JSON, form data, content_length, validação de status e links para API REST em Zig."
date: "2026-02-21"
author: "Zig Brasil"
---

# Zig HTTP POST: enviar JSON e formulários com std.http.Client

Receita prática de Zig HTTP POST com std.http.Client: JSON, form data, content_length, validação de status e links para API REST em Zig.


## Introdução

Requisições HTTP POST são usadas para enviar dados ao servidor, como payloads JSON, formulários e comandos para uma API REST. Diferente do GET, que apenas solicita dados, o POST normalmente cria recursos, dispara uma ação ou envia um corpo que precisa ser validado no outro lado.

Nesta receita, você aprenderá a fazer **HTTP POST em Zig** usando `std.http.Client`, com `Content-Type`, `content_length`, escrita do corpo, validação de status e leitura segura da resposta. Se você quer o guia completo de cliente HTTP, leia [Zig HTTP Client: GET, POST, JSON e APIs REST em Zig](/tutoriais/zig-http-client/). Se o objetivo é receber esse POST em um serviço próprio, combine com [Zig Server HTTP: API REST com std.http.Server](/tutoriais/zig-http-server/).

## Resposta rápida: POST em Zig com std.http.Client

| Objetivo | Caminho em Zig | Próximo link |
|---|---|---|
| Enviar JSON para uma API REST | `client.open(.POST, uri, ...)`, header `Content-Type: application/json`, `content_length` e `req.write()` | Continue nesta receita |
| Gerar JSON a partir de uma struct | `std.json.stringify` em um `ArrayList(u8)` antes de abrir o POST | [Como gerar JSON em Zig](/receitas/zig-gerar-json/) |
| Fazer um GET antes do POST | `client.open(.GET, ...)` e validação de status | [Como fazer requisições HTTP GET em Zig](/receitas/zig-http-get-request/) |
| Entender a API da biblioteca padrão | `std.http.Client.fetch`, `open`, headers, TLS e storage da resposta | [std.http.Client em Zig](/stdlib/std-http-client/) |
| Criar a API que recebe o POST | `std.http.Server`, roteamento, JSON e respostas explícitas | [Zig Server HTTP: API REST com std.http.Server](/tutoriais/zig-http-server/) |

O fluxo mínimo é sempre o mesmo: parsear a URL, abrir a requisição com método `.POST`, declarar os headers, definir o tamanho do corpo, chamar `send()`, escrever o payload, finalizar com `finish()`, aguardar com `wait()` e validar `req.status` antes de confiar na resposta.

## Pré-requisitos

- Zig instalado (versão 0.13+). Veja o [guia de instalação](/tutoriais/como-instalar-zig/)
- Conhecimento básico de Zig. Consulte a [introdução ao Zig](/tutoriais/introducao-ao-zig/)
- Familiaridade com [requisições HTTP GET](/receitas/zig-http-get-request/)
- Noções de JSON. Consulte [Parsing JSON em Zig](/tutoriais/parsing-json-zig/) quando a resposta também precisar ser interpretada.

## POST com Corpo JSON

O caso mais comum é enviar JSON para uma API. Em Zig, a parte importante é tornar o contrato HTTP explícito: header correto, `content_length` definido e status validado depois da resposta.

```zig
const std = @import("std");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse("https://httpbin.org/post");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.POST, uri, .{
        .server_header_buffer = &header_buf,
        .extra_headers = &.{
            .{ .name = "Content-Type", .value = "application/json" },
            .{ .name = "Accept", .value = "application/json" },
            .{ .name = "User-Agent", .value = "ZigBrasilPost/1.0" },
        },
    });
    defer req.deinit();

    // Definir o corpo da requisição
    const json_body =
        \\{"nome": "Maria Silva", "email": "maria@exemplo.com", "idade": 28}
    ;
    req.transfer_encoding = .{ .content_length = json_body.len };

    try req.send();

    // Escrever o corpo
    var written: usize = 0;
    while (written < json_body.len) {
        written += try req.write(json_body[written..]);
    }

    try req.finish();
    try req.wait();

    if (req.status != .ok and req.status != .created) {
        std.debug.print("Erro HTTP: {}\n", .{req.status});
        return;
    }

    // Ler resposta
    const body = try req.reader().readAllAlloc(allocator, 1024 * 1024);
    defer allocator.free(body);

    std.debug.print("Status: {}\n", .{req.status});
    std.debug.print("Resposta: {s}\n", .{body});
}
```

Esse exemplo usa `httpbin.org` apenas como destino de teste. Em produção, prefira URLs configuráveis, timeout definido na borda da aplicação e limite de resposta menor quando você conhece o tamanho esperado. O guia [Zig HTTP Server em Produção](/artigos/zig-http-server-producao/) cobre o outro lado desse contrato: limites de corpo, logs, health check e proxy.

### Saída esperada

```
Status: http.Status.ok
Resposta: {
  "data": "{\"nome\": \"Maria Silva\", \"email\": \"maria@exemplo.com\", \"idade\": 28}",
  ...
}
```

## POST com Struct Serializada para JSON

Serialize uma struct Zig diretamente para JSON antes de enviar:

```zig
const std = @import("std");

const Usuario = struct {
    nome: []const u8,
    email: []const u8,
    idade: u32,
    ativo: bool,
};

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Criar o objeto a ser enviado
    const usuario = Usuario{
        .nome = "João Santos",
        .email = "joao@exemplo.com",
        .idade = 35,
        .ativo = true,
    };

    // Serializar para JSON
    var json_buf = std.ArrayList(u8).init(allocator);
    defer json_buf.deinit();

    try std.json.stringify(usuario, .{}, json_buf.writer());
    const json_body = json_buf.items;

    std.debug.print("JSON a enviar: {s}\n", .{json_body});

    // Enviar a requisição POST
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse("https://httpbin.org/post");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.POST, uri, .{
        .server_header_buffer = &header_buf,
        .extra_headers = &.{
            .{ .name = "Content-Type", .value = "application/json" },
            .{ .name = "Accept", .value = "application/json" },
        },
    });
    defer req.deinit();

    req.transfer_encoding = .{ .content_length = json_body.len };

    try req.send();
    var written: usize = 0;
    while (written < json_body.len) {
        written += try req.write(json_body[written..]);
    }
    try req.finish();
    try req.wait();

    if (req.status == .ok or req.status == .created) {
        std.debug.print("Usuário criado com sucesso!\n", .{});
    } else {
        std.debug.print("Erro: {}\n", .{req.status});
    }
}
```

## POST com Form URL-Encoded

Envie dados de formulário no formato `application/x-www-form-urlencoded`:

```zig
const std = @import("std");

fn urlEncode(allocator: std.mem.Allocator, params: []const [2][]const u8) ![]u8 {
    var result = std.ArrayList(u8).init(allocator);
    errdefer result.deinit();

    for (params, 0..) |param, i| {
        if (i > 0) try result.append('&');

        // Chave
        for (param[0]) |c| {
            if (std.ascii.isAlphanumeric(c) or c == '-' or c == '_' or c == '.' or c == '~') {
                try result.append(c);
            } else if (c == ' ') {
                try result.append('+');
            } else {
                try result.appendSlice(&.{ '%', std.fmt.digitToChar(c >> 4, .upper), std.fmt.digitToChar(c & 0xf, .upper) });
            }
        }

        try result.append('=');

        // Valor
        for (param[1]) |c| {
            if (std.ascii.isAlphanumeric(c) or c == '-' or c == '_' or c == '.' or c == '~') {
                try result.append(c);
            } else if (c == ' ') {
                try result.append('+');
            } else {
                try result.appendSlice(&.{ '%', std.fmt.digitToChar(c >> 4, .upper), std.fmt.digitToChar(c & 0xf, .upper) });
            }
        }
    }

    return result.toOwnedSlice();
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Codificar parâmetros do formulário
    const params = [_][2][]const u8{
        .{ "usuario", "maria_silva" },
        .{ "senha", "minha_senha_123" },
        .{ "lembrar", "true" },
    };

    const form_body = try urlEncode(allocator, &params);
    defer allocator.free(form_body);

    std.debug.print("Form body: {s}\n", .{form_body});

    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse("https://httpbin.org/post");

    var header_buf: [4096]u8 = undefined;
    var req = try client.open(.POST, uri, .{
        .server_header_buffer = &header_buf,
        .extra_headers = &.{
            .{ .name = "Content-Type", .value = "application/x-www-form-urlencoded" },
        },
    });
    defer req.deinit();

    req.transfer_encoding = .{ .content_length = form_body.len };

    try req.send();
    var written: usize = 0;
    while (written < form_body.len) {
        written += try req.write(form_body[written..]);
    }
    try req.finish();
    try req.wait();

    std.debug.print("Status: {}\n", .{req.status});
}
```

## Função POST Reutilizável

Uma função genérica ajuda quando o POST aparece em mais de um ponto do programa. Mantenha o tamanho máximo da resposta e os status aceitos perto da função, para não espalhar política HTTP pelo código.

```zig
const std = @import("std");

const PostResponse = struct {
    status: std.http.Status,
    body: []const u8,
    allocator: std.mem.Allocator,

    pub fn deinit(self: *PostResponse) void {
        self.allocator.free(self.body);
    }
};

fn httpPost(
    allocator: std.mem.Allocator,
    url: []const u8,
    content_type: []const u8,
    payload: []const u8,
) !PostResponse {
    var client = std.http.Client{ .allocator = allocator };
    defer client.deinit();

    const uri = try std.Uri.parse(url);

    var header_buf: [8192]u8 = undefined;
    var req = try client.open(.POST, uri, .{
        .server_header_buffer = &header_buf,
        .extra_headers = &.{
            .{ .name = "Content-Type", .value = content_type },
            .{ .name = "Accept", .value = "application/json" },
            .{ .name = "User-Agent", .value = "ZigBrasilPost/1.0" },
        },
    });
    defer req.deinit();

    req.transfer_encoding = .{ .content_length = payload.len };

    try req.send();
    var written: usize = 0;
    while (written < payload.len) {
        written += try req.write(payload[written..]);
    }
    try req.finish();
    try req.wait();

    if (req.status != .ok and req.status != .created and req.status != .accepted) {
        return error.HttpStatusRejected;
    }

    const body = try req.reader().readAllAlloc(allocator, 4 * 1024 * 1024);

    return .{
        .status = req.status,
        .body = body,
        .allocator = allocator,
    };
}

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    const json =
        \\{"titulo": "Meu Post", "conteudo": "Olá mundo!"}
    ;

    var resp = try httpPost(
        allocator,
        "https://httpbin.org/post",
        "application/json",
        json,
    );
    defer resp.deinit();

    std.debug.print("Status: {}\n", .{resp.status});
    std.debug.print("Corpo ({d} bytes)\n", .{resp.body.len});
}
```

## Dicas e Boas Práticas

1. **Sempre defina Content-Type**: O servidor precisa saber o formato dos dados enviados.

2. **Defina `transfer_encoding`**: Sempre configure o `content_length` antes de enviar o corpo.

3. **Defina `Accept` e `User-Agent`**: APIs públicas e internas ficam mais fáceis de depurar quando o cliente declara o formato esperado e se identifica.

4. **Trate erros de rede**: Conexões podem falhar. Use [tratamento de erros](/tutoriais/tratamento-de-erros-em-zig/) adequado e diferencie erro de conexão, status HTTP rejeitado e JSON inválido.

5. **Valide a resposta**: Verifique sempre o código de status HTTP retornado antes de fazer parse do corpo.

6. **Limite memória de resposta**: `readAllAlloc` deve receber um teto coerente com a API. Não use limites enormes por padrão em CLIs, workers ou serviços.

7. **Serialize corretamente**: Use [geração de JSON](/receitas/zig-gerar-json/) para criar payloads complexos em vez de montar JSON manualmente.

8. **Conecte com o resto da aplicação**: Se o POST faz parte de um backend, leia também [filas e workers em background](/artigos/zig-filas-workers-background/) e [observabilidade em Zig](/artigos/zig-observabilidade/) para decidir retry, logs e métricas.

## Receitas Relacionadas

- [Como fazer requisições HTTP GET em Zig](/receitas/zig-http-get-request/) - Requisições de leitura
- [Como parsear JSON em Zig](/receitas/zig-parse-json/) - Processar respostas JSON
- [Como gerar JSON em Zig](/receitas/zig-gerar-json/) - Serializar dados para envio
- [Como parsear URLs em Zig](/receitas/zig-parse-url/) - Manipulação de URLs
- [std.http.Client em Zig](/stdlib/std-http-client/) - Referência da API padrão

## Tutoriais Relacionados

- [Zig HTTP Client: GET, POST, JSON e APIs REST em Zig](/tutoriais/zig-http-client/)
- [Zig Server HTTP: API REST com std.http.Server](/tutoriais/zig-http-server/)
- [Parsing JSON em Zig](/tutoriais/parsing-json-zig/)
- [Tratamento de Erros em Zig](/tutoriais/tratamento-de-erros-em-zig/)
- [Zig HTTP Server em Produção](/artigos/zig-http-server-producao/)
