I'm a developer. I don't need Postman to run HTTP requests
Don't get me wrong, Postman is a perfect tool in its niche, but it is still a UI tool. Starting from the moment Postman came to market, I've been searching for a way to execute my requests in the way I’m used to — by writing code. The first tool I tried was the REST Client VS Code extension. It was very close to what I needed - finally, I could run requests from my IDE. I didn’t need to choose a verb from a dropdown, and I could store the files in Git. But there were still some drawbacks: I didn’t like how requests opened in a separate tab. If I switched focus to that tab, the next time VS Code would open my file in that tab group. Some syntax constructions, like ###, looked weird to me. I could get used to them, but they still felt unnatural. I still had to specify the full URL, even when using variables. I couldn’t just define a base URL and then use GET /users. I also wanted OpenAPI IntelliSense, but that still wasn’t possible. So, all of this led me to create my own language. HTTL For now, it is available as a VS Code Extension but very soon, I plan to roll out a CLI and support for other IDEs. In order to understand how this language happened and what the motivation behind it was, let's start from the basics and imagine how we could evolve the standard HTTP specification. I'll take this standard request to create a new user: First of all, let's do a little cleaning. Obviously, we don't need the Content-Length header - the language should be able to calculate it by itself. We can also remove the HTTP/1.1 part and, for simplicity, use lowercase for verbs. Ok, now we have this: Not much better, but let's go ahead. Host - this is an interesting one. While the HTTP spec requires it, the header does not indicate the communication protocol (e.g., HTTP or HTTPS), so we need to specify it directly somewhere else. Option #1: Use a full url in the request line But what if we have more than one request and we don't want to repeat ourselves? Option #2: Use a kind of templating, like in the REST client. Again, we have to include {{baseHost}} in every request. But don't you think we can do better? That's why we're here - we need to introduce a new language construction that extends the standard HTTP protocol: a directive. Here's one: @base. It will allow us to avoid repeating the base URL and prevent the need to specify it every time in any subsequent request. Ok, see what we've got. Slightly better, but let's move forward. Content-Type - we can make it common for every request to avoid repeating ourselves again. We can simply move it out of the request construction. With this change, Content-Type becomes a global header and will be automatically set for any subsequent requests. It's very convenient for the Authentication header. On a final note, we are sending JSON, so why do we need Content-Type at all? The language should automatically set the header for obviously recognizable payloads. Additionally, we can use JavaScript object syntax to remove unnecessary quotes. And here is what HTTL is: Much cleaner, but having just one request might not be as impressive as handling a chain of requests. And yes, this is the next feature the language should support: request chaining. Request Chaining Let's consider a real example where we first need to get an authentication token and then use it in subsequent requests. With all we've done above, let's dive in. This time, let's use the ReqRes.in API for this showcase. We are slowly moving to a new feature: variables. Variables We can store the response in a variable and use it in a later request. Here is how: Firstly, we introduced a new construction here - as auth. It tells the runtime to store the response body in this variable. Secondly, we set a global Authorization header to allow any subsequent request to use this header. Authorization: Bearer {auth.token} - a few notes here: For string interpolation, we can use single brackets {}. If the response is JSON, we can use dot notation to access nested fields. You may wonder how to access other elements of the response, such as headers, timings, status, etc. I plan to add this in future releases Now, we can complete the scenario of create, update, and delete steps. That's much better! We are not overwhelmed with unnecessary constructions like the base URL, common headers, etc. That's one of the main ideas of HTTL: to remove unnecessary constructions and leave only meaningful elements. It keeps the code clean and lets us focus more on logic rather than mechanics. Wait, Assert? Yes! How can we create a language without testing capabilities? Asserting The assert keyword can be added to the end of any request to verify the following elements: status, headers, and body. So far, it allows only strict equality, like status: 200. But stay tuned—the testing feature will evolve signifi
![I'm a developer. I don't need Postman to run HTTP requests](https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyjvbwxmz30volo2khhcy.png)
Don't get me wrong, Postman is a perfect tool in its niche, but it is still a UI tool.
Starting from the moment Postman came to market, I've been searching for a way to execute my requests in the way I’m used to — by writing code.
The first tool I tried was the REST Client VS Code extension. It was very close to what I needed - finally, I could run requests from my IDE. I didn’t need to choose a verb from a dropdown, and I could store the files in Git.
But there were still some drawbacks:
- I didn’t like how requests opened in a separate tab. If I switched focus to that tab, the next time VS Code would open my file in that tab group.
- Some syntax constructions, like ###, looked weird to me. I could get used to them, but they still felt unnatural.
- I still had to specify the full URL, even when using variables. I couldn’t just define a base URL and then use
GET /users
. - I also wanted OpenAPI IntelliSense, but that still wasn’t possible.
So, all of this led me to create my own language.
HTTL
For now, it is available as a VS Code Extension
but very soon, I plan to roll out a CLI and support for other IDEs.
In order to understand how this language happened and what the motivation behind it was, let's start from the basics and imagine how we could evolve the standard HTTP specification.
I'll take this standard request to create a new user:
First of all, let's do a little cleaning.
Obviously, we don't need the Content-Length
header - the language should be able to calculate it by itself. We can also remove the HTTP/1.1
part and, for simplicity, use lowercase for verbs.
Ok, now we have this:
Not much better, but let's go ahead.
Host
- this is an interesting one. While the HTTP spec requires it, the header does not indicate the communication protocol (e.g., HTTP or HTTPS), so we need to specify it directly somewhere else.
Option #1: Use a full url in the request line
But what if we have more than one request and we don't want to repeat ourselves?
Option #2: Use a kind of templating, like in the REST client.
Again, we have to include {{baseHost}}
in every request.
But don't you think we can do better?
That's why we're here - we need to introduce a new language construction that extends the standard HTTP protocol: a directive. Here's one: @base
.
It will allow us to avoid repeating the base URL and prevent the need to specify it every time in any subsequent request.
Ok, see what we've got.
Slightly better, but let's move forward.
Content-Type
- we can make it common for every request to avoid repeating ourselves again. We can simply move it out of the request construction.
With this change, Content-Type
becomes a global header and will be automatically set for any subsequent requests. It's very convenient for the Authentication header.
On a final note, we are sending JSON, so why do we need Content-Type
at all? The language should automatically set the header for obviously recognizable payloads.
Additionally, we can use JavaScript object syntax to remove unnecessary quotes.
And here is what HTTL is:
Much cleaner, but having just one request might not be as impressive as handling a chain of requests.
And yes, this is the next feature the language should support: request chaining.
Request Chaining
Let's consider a real example where we first need to get an authentication token and then use it in subsequent requests.
With all we've done above, let's dive in.
This time, let's use the ReqRes.in API for this showcase.
We are slowly moving to a new feature: variables.
Variables
We can store the response in a variable and use it in a later request.
Here is how:
Firstly, we introduced a new construction here - as auth
. It tells the runtime to store the response body in this variable.
Secondly, we set a global Authorization
header to allow any subsequent request to use this header.
Authorization: Bearer {auth.token}
- a few notes here:
- For string interpolation, we can use single brackets {}.
- If the response is JSON, we can use dot notation to access nested fields.
You may wonder how to access other elements of the response, such as headers, timings, status, etc. I plan to add this in future releases
Now, we can complete the scenario of create, update, and delete steps.
That's much better! We are not overwhelmed with unnecessary constructions like the base URL, common headers, etc.
That's one of the main ideas of HTTL: to remove unnecessary constructions and leave only meaningful elements. It keeps the code clean and lets us focus more on logic rather than mechanics.
Wait, Assert? Yes! How can we create a language without testing capabilities?
Asserting
The assert
keyword can be added to the end of any request to verify the following elements: status, headers, and body.
So far, it allows only strict equality, like status: 200
.
But stay tuned—the testing feature will evolve significantly in upcoming releases.
OpenAPI
OpenAPI: we cannot overlook this important element of modern API development.
This was my personal goal - to finally have IntelliSense for the OpenAPI spec. I’m a big fan of API documentation because it helps me make fewer mistakes.
Since the HTTP spec does not include anything related to the OpenAPI spec, we need to introduce something new - the @spec
directive.
All we need to do is specify a URL to the openapi.json file, and now we can simply type get /
or invoke IDE IntelliSense, and the IDE will show all possible methods and URLs.
Please note that the @spec
directive will automatically set the @base
URL based on the OpenAPI spec.
So that's it for now! I hope you liked this journey.
We haven't touched the module system, environment variables, and some cool features for VS Code, but let's leave those for the next articles. :)
Also, if this is interesting, I can share more about the creation process, including ANTLR.js, the VS Code extension, implementing the Language Server Protocol (LSP), IDE IntelliSense, formatting the code, managing the runtime, and more, just let me know in the comments!
Thank you and Happy coding!
Nothing can stop you from reaching out to me :)