Skip to content
Nicolas Demay
JetBrains HTTP Client: test your APIs without leaving your IDE

JetBrains HTTP Client: test your APIs without leaving your IDE

Published on 6 min read

When developing an API, the typical reflex is to open Postman, or more recently Bruno, to fire a few requests and check that everything works. It gets the job done, but it means constantly switching context. You code in PhpStorm, alt-tab to Postman, come back, tab again…

You might not know that every JetBrains IDE (PhpStorm, IntelliJ, WebStorm, GoLand…) ships with a full HTTP client, right inside the editor. .http files versioned with the project, executable in one click, with environment variables, scripts, GraphQL support and OpenAPI autocompletion.

I’ve been using it daily for over two years, and I have no desire to go back.

The basics: GET, POST and headers

Create a .http (or .rest) file anywhere in the project. The syntax is minimal:

api-tests.http
### List all users
GET http://localhost:8080/api/users
Accept: application/json

For a POST with a JSON body, just add the content after a blank line:

api-tests.http
### Create a user
POST http://localhost:8080/api/users
Content-Type: application/json
{
"name": "Nicolas Demay",
"email": "[email protected]"
}

Headers go right after the request line, with no blank line between them. The ### separator lets you chain multiple requests in the same file and name them for easy navigation.

api-tests.http
### Authentication
POST http://localhost:8080/api/login
Content-Type: application/json
{
"username": "admin",
"password": "secret"
}
### Get profile (protected request)
GET http://localhost:8080/api/profile
Authorization: Bearer {{auth_token}}
Accept: application/json

You can run each request individually via the play icon in the gutter, or all of them at once. The response shows up in a dedicated panel with the status code, headers, and formatted body.

By the way, quick tip: if you have an XHR request causing trouble in the browser, right-click in DevTools → “Copy as cURL”, then paste it directly into a .http file. The IDE converts the cURL into HTTP Client syntax automatically. No need to rewrite the headers by hand.

Variables and environments

Define environments in an http-client.env.json file, either at the project root or in the same folder as the .http file:

http-client.env.json
{
"dev": {
"host": "http://localhost:8080",
"api_version": "v1"
},
"staging": {
"host": "https://staging.example.com",
"api_version": "v1"
},
"production": {
"host": "https://api.example.com",
"api_version": "v2"
}
}

Secrets go in a separate http-client.private.env.json file (add it to .gitignore — we’ve all pushed a token to a public repo at least once):

http-client.private.env.json
{
"dev": {
"api_token": "dev-token-xxx"
},
"staging": {
"api_token": "staging-token-yyy"
}
}

Then use the {{variable}} syntax in your requests:

api-tests.http
### List products
GET {{host}}/api/{{api_version}}/products
Authorization: Bearer {{api_token}}
Accept: application/json

Before running, a dropdown at the top of the editor lets you pick the target environment. Values from the private file override the shared one. So the team shares URLs and structure, everyone keeps their own tokens.

Environment selection in the JetBrains HTTP Client Run with dropdown

The IDE also provides built-in dynamic variables:

api-tests.http
### Create a resource with a unique ID
POST {{host}}/api/items
Content-Type: application/json
{
"id": "{{$uuid}}",
"createdAt": "{{$isoTimestamp}}",
"priority": {{$randomInt}}
}

Pre-request and response handlers

Response handlers (with >) run after the response and let you test assertions or store values:

api-tests.http
### Login and store the token
POST {{host}}/api/login
Content-Type: application/json
{
"username": "admin",
"password": "{{password}}"
}
> {%
client.test("Login successful", function() {
client.assert(response.status === 200, "Expected status: 200");
});
// Store the token for subsequent requests
client.global.set("auth_token", response.body.token);
%}

The token is now available via {{auth_token}} in all subsequent requests. You can chain calls with data dependencies.

Pre-request scripts (with <) run before sending and let you prepare data:

api-tests.http
< {%
// Generate a signature before sending
const timestamp = Date.now().toString();
request.variables.set("timestamp", timestamp);
request.variables.set("requestId", $random.uuid);
%}
POST {{host}}/api/secure
Content-Type: application/json
X-Timestamp: {{timestamp}}
X-Request-Id: {{requestId}}
{
"data": "payload"
}

In short, you get client.log() for debugging and client.test() for building real test suites. Nothing groundbreaking, but it gets the job done without leaving the editor.

File uploads

To send a file in a request, use the < syntax followed by the path:

api-tests.http
### Simple upload (body = file content)
POST {{host}}/api/import
Content-Type: application/json
< ./data/import.json
### Multipart upload (form with file)
POST {{host}}/api/upload
Content-Type: multipart/form-data; boundary=boundary
--boundary
Content-Disposition: form-data; name="file"; filename="document.pdf"
Content-Type: application/pdf
< ./files/document.pdf
--boundary
Content-Disposition: form-data; name="description"
My uploaded file
--boundary--

Note: < reads a file as input (into the body), > is reserved for response handlers. Don’t mix them up.

GraphQL support

The HTTP client natively supports GraphQL with the GRAPHQL keyword:

graphql-tests.http
### Query
GRAPHQL {{host}}/graphql
query {
users {
id
name
email
}
}
### Mutation with variables
GRAPHQL {{host}}/graphql
mutation ($input: CreateUserInput!) {
createUser(input: $input) {
id
name
}
}
{
"input": {
"name": "Nicolas",
"email": "[email protected]"
}
}

What if the IDE already knows your API?

If the project contains an OpenAPI specification (or points to a remote file), the IDE uses it to enrich .http files:

  • URL autocompletion: endpoints defined in the spec are suggested as you type
  • JSON body autocompletion: required fields are proposed automatically

Same principle for GraphQL: install the GraphQL plugin (free on the JetBrains Marketplace). Once enabled, it introspects the endpoint schema and provides autocompletion on types, fields and arguments directly in the .http file.

This is where the advantage over Postman is most obvious: the IDE already knows the code and the API docs. No manual import, no synchronization to maintain.

The real win: one-click debugging

This might be the least visible advantage, but it’s the one that saves me the most time. When developing an API in PhpStorm, you can set an Xdebug breakpoint in the code, then rerun the HTTP request in a single click.

No need to go back to the .http file every time. The play button at the top of the IDE remembers the last command executed — whether it was a unit test or an HTTP request. Edit the code, set a breakpoint, click play, and you’re straight in the debugger with the right request. The edit → test → debug cycle takes a few seconds, without switching windows.

With Postman, this workflow is broken: you have to alt-tab, rerun, come back. Here, everything stays on the same screen.

What about AI in the loop?

The real bonus of this format is that .http files live in the repo like any other code file. Versioned, readable, shareable. And that means an assistant like Claude has direct access to them in the project context.

In practice, I regularly ask it to generate an .http request for an endpoint I just created. It knows the code, it knows the schema — the file is ready in seconds. Try doing that with a Postman collection.