Uma alternativa ao JSONPlaceholder em que as escritas ficam salvas
Os mesmos tipos de recurso, mas as escritas ficam salvas, entradas ruins são rejeitadas e os erros estão disponíveis quando você quiser.
Exemplos executados pela última vez contra a API em
O JSONPlaceholder finge as escritas: um POST devolve id: 101, mas um GET desse id devolve 404, e um DELETE deixa o registro onde estava. Se o seu app precisa ler de volta o que acabou de escrever, use https://api.testingapis.com/apientre para ver. Ela tem os mesmos tipos de recurso (posts, comentários, álbuns, fotos, tarefas e usuários), e criar, atualizar e apagar fica salvo. Ela também rejeita entradas inválidas com 400 e pode fazer qualquer requisição falhar quando você quiser, então dá para construir e testar os estados de erro também. É gratuita, não precisa de chave e aceita requisições de qualquer origem.
Escritas que ficam salvas
Isto é o que o JSONPlaceholder faz com um post novo:
$ curl -X POST https://jsonplaceholder.typicode.com/posts \
-H "Content-Type: application/json" \
-d '{"title":"hello","body":"world","userId":1}'
{ "title": "hello", "body": "world", "userId": 1, "id": 101 }
$ curl -i https://jsonplaceholder.typicode.com/posts/101
HTTP/1.1 404 Not FoundOs mesmos passos aqui: criar um post, ler de volta, editar, apagar e conferir que sumiu.
$ curl -X POST https://api.testingapis.com/api/Posts \
-H "Content-Type: application/json" \
-d '{"userId":101,"title":"Hello from the guide","body":"Checking that writes persist."}'
{"message":"Post created successfully","data":{"id":16,"userId":101,"title":"Hello from the guide","body":"Checking that writes persist.","tags":[],"publishedAt":"2026-10-07T02:58:58Z","likes":0}}
$ curl https://api.testingapis.com/api/Posts/16
{"id":16,"userId":101,"title":"Hello from the guide","body":"Checking that writes persist.","tags":[],"publishedAt":"2026-10-07T02:58:58Z","likes":0}
$ curl -X PUT https://api.testingapis.com/api/Posts/16 \
-H "Content-Type: application/json" \
-d '{"userId":101,"title":"Edited title","body":"Checking that writes persist."}'
{"message":"Post updated successfully","data":{"id":16,"userId":101,"title":"Edited title",...}}
$ curl -X DELETE https://api.testingapis.com/api/Posts/16
{"message":"Post deleted successfully"}
$ curl -i https://api.testingapis.com/api/Posts/16
HTTP/1.1 404 Not Found
{"status":404,"error":"Not Found","message":"Post with ID 16 does not exist."}Correspondência de rotas
| JSONPlaceholder | testingapis.com | Registros |
|---|---|---|
/posts | /api/posts | 15 (JSONPlaceholder: 100) |
/posts/1/comments | /api/posts/1/comments | Comentários do post 1 |
/comments | /api/commentsdocs | 50 (500) |
/albums | /api/albumsdocs | 10 (100) |
/albums/1/photos | /api/albums/1/photosentre para ver | 5 por álbum |
/photos | /api/photosdocs | 50 (5.000) |
/todos | /api/todos | 30 (200) |
/users | /api/peopledocs | 20 (10), com endereço, coordenadas e empresa |
/users/1/posts | /api/posts/user/101 | Os ids de usuário começam em 101 |
Os caminhos não diferenciam maiúsculas: /api/posts e /api/Posts são a mesma coisa. Há menos registros que no JSONPlaceholder, mas cada um tem mais campos. Os posts têm tags, curtidas e data de publicação, as tarefas têm prioridade e prazo, e as pessoas têm endereço e empresa aninhados. A documentação da API lista mais 13 tipos de recurso, de produtos e carrinhos a livros, filmes e países.
/api/posts/1Entrada ruim recebe erro
O JSONPlaceholder aceita qualquer corpo, até um vazio. Aqui, um campo obrigatório ausente devolve 400 com uma mensagem, então o caminho de erro do seu formulário tem algo real para mostrar:
$ curl -i -X POST https://jsonplaceholder.typicode.com/posts -H "Content-Type: application/json" -d '{}'
HTTP/1.1 201 Created
{ "id": 101 }
$ curl -i -X POST https://api.testingapis.com/api/Posts -H "Content-Type: application/json" -d '{"title":""}'
HTTP/1.1 400 Bad Request
{"status":400,"error":"Bad Request","message":"Missing required field: title"}Erros e respostas lentas quando quiser
Adicione ?error=500 a qualquer requisição para fazê-la falhar com esse status, ou ?delay=3 para responder três segundos depois. Juntos, ?delay=2&error=503 mostra um estado de carregamento seguido de um erro. O guia de respostas de erro cobre timeouts, retries e limites de taxa.
/api/todos/1?error=503Filtros, ordenação e paginação
As duas APIs filtram listas por campo, como em ?userId=101, e as duas ordenam, buscam e paginam. Os nomes dos parâmetros mudam:
| JSONPlaceholder | testingapis.com | |
|---|---|---|
| Filtrar | ?userId=1 | ?userId=101 |
| Paginar | ?_page=2&_limit=10 | ?page=2&limit=10 |
| Ordenar | ?_sort=id&_order=desc | ?sort=id&order=desc |
| Buscar | ?q=word | ?q=word |
| Total | X-Total-Count | X-Total-Count, X-Total-Pages |
Migrando um app
As leituras devolvem o registro diretamente, como o JSONPlaceholder. As escritas o embrulham com uma mensagem, como { message, data }, e os ids de usuário começam em 101. São as duas mudanças de que a maioria dos apps precisa:
// Before
const BASE = "https://jsonplaceholder.typicode.com";
const post = await (await fetch(`${BASE}/posts/1`)).json();
const created = await (await fetch(`${BASE}/posts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: 1, title: "Hi", body: "There" }),
})).json();
// After
const BASE = "https://api.testingapis.com/api";
const post = await (await fetch(`${BASE}/posts/1`)).json();
const { data: created } = await (await fetch(`${BASE}/posts`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ userId: 101, title: "Hi", body: "There" }),
})).json();Quando o JSONPlaceholder é a melhor escolha
- Você precisa de listas grandes, como 5.000 fotos para testar rolagem virtual.
- Você está seguindo um tutorial que usa exatamente os ids e campos do JSONPlaceholder.
- Você só lê dados e nunca precisa que uma escrita apareça depois.
Para ir além, a lição de teste CRUD mostra como conferir que cada criação, atualização e exclusão aconteceu de verdade.