post-they
v0.2.0
Published
Write Postman collections as code. Full IntelliSense, imports/exports, auto-looping, TypeScript support. Compile and run API tests from the terminal.
Maintainers
Readme
post-they
Write Postman collections as code. Get full IntelliSense in VS Code, use imports/exports, loop over data without boilerplate — then compile and run your API tests directly from the terminal.
No Postman GUI needed. The output is a standard collection.json that runs anywhere — in post-they, Newman, or Postman itself.
Why?
Postman is great for exploring APIs, but once you need version control, code reuse, and a proper dev workflow it gets in the way. post-they lets you:
- Write requests and test scripts as regular JavaScript files
- Use
import/exportfor shared helpers and test data - Get full autocomplete for the
pmAPI (variables, request, response, assertions) - Loop over test data with zero boilerplate
- Compile to a standard Postman collection that runs anywhere Newman does
- Import existing Postman collections and keep working in code
Install
As a project devDependency (recommended):
npm install --save-dev post-theyThen add scripts to your package.json:
{
"scripts": {
"test": "post-they run",
"build": "post-they build"
}
}Now npm test compiles and runs your collection.
You can also use npx without installing — handy for init and one-off usage:
npx post-they init
npx post-they runQuick start
# Create a new project (scaffolds src/ + a full example in src-example/)
npx post-they init
# Or name the folder — useful in mono-repos with multiple test suites
npx post-they init api-tests
# Try the built-in example (starts a test server, runs full CRUD tests)
npx post-they example
# Write your own requests in src/requests/, then run:
npx post-they run
# Run from a named folder
npx post-they run api-tests
# Import an existing Postman collection
npx post-they import my-collection.jsonProject structure
src/
collection.js # Collection name, request order, collection-level scripts
data/
pets.json # Test data (plain JSON)
helpers/
validate.js # Shared helper functions
requests/
get-all-pets.js # One file per request
create-pets.js
read-pets.jsMultiple test suites (mono-repo)
You can have several independent test suites in one repo. Pass a folder name to init:
npx post-they init api-tests
npx post-they init smoke-tests
npx post-they init api-tests --ts # TypeScript variantEach gets its own collection.js, requests/, data/, and helpers/. Then wire them up in package.json:
{
"scripts": {
"test:api": "post-they run api-tests",
"test:smoke": "post-they run smoke-tests",
"test": "npm run test:api && npm run test:smoke"
}
}build and import accept folder arguments the same way:
npx post-they build api-tests -o api-collection.json
npx post-they import existing.json api-testsDefining requests
Each request is a JavaScript file. The default export is the request definition, and named exports handle scripts and data.
export default {
method: 'GET',
url: '{{baseUrl}}/pets'
};
export function postResponse() {
const pets = pm.response.json();
pm.test('Status code is 200', () => pm.response.to.have.status(200));
pm.test('Response is an array', () => pm.expect(pets).to.be.an('array'));
}Available exports
| Export | Type | Description |
|--------|------|-------------|
| default | object | The request definition (method, url, body, headers) |
| data | array | Test data to loop over (see Looping over data) |
| preRequest | function | Runs before the request is sent |
| postResponse | function | Runs after the response is received |
The pm object
Inside preRequest and postResponse functions you have access to the full Postman sandbox API via the global pm object — with complete IntelliSense in VS Code.
pm.variables.get('key') // Get a variable
pm.variables.set('key', value) // Set a variable
pm.response.json() // Parse response body as JSON
pm.response.to.have.status(200) // Assert status code
pm.test('name', () => { ... }) // Named test assertion
pm.expect(value).to.equal(expected) // Chai-style assertion
pm.sendRequest(url, callback) // Send additional requests
pm.info.requestName // Current request name
pm.execution.setNextRequest(name) // Control execution flowLooping over data
Export a data array and your request will automatically loop over each item. The current item is passed as a parameter to preRequest and postResponse — no counters, no manual looping.
import pets from '../data/pets.json' with { type: 'json' };
export { pets as data };
export default {
method: 'POST',
url: '{{baseUrl}}/pets',
body: {
name: '{{petName}}',
species: '{{petSpecies}}'
}
};
export function preRequest(pet) {
pm.variables.set('petName', pet.name);
pm.variables.set('petSpecies', pet.species);
}
export function postResponse(pet) {
pm.test('Status 200', () => pm.response.to.have.status(200));
const result = pm.response.json();
pet.id = result.lastInsertRowid; // Store for later requests
}The compiler handles all the counter management and setNextRequest logic behind the scenes.
Reusing requests and test flows
The same request can appear multiple times in your collection order. This is useful for verifying data at different stages — create, verify, update, verify again, delete:
export const order = [
getAllPets,
createPets,
readPets, // verify after create
updatePets,
readPets, // verify after update — same request, no duplication
deletePets
];The compiler generates a single item in the collection and uses a route map to navigate correctly between phases. No code duplication.
Design your dummy data for real testing
Since looping is free, put effort into your test data instead of your test plumbing. Write dummy data that challenges your API — test edge cases like string truncation, special characters, type coercion, empty values, and boundary conditions:
[
{ "name": "O'Malley", "species": "cat" },
{ "name": "", "species": "unknown" },
{ "name": "A very long pet name that might exceed your database column limit", "species": "dog" },
{ "name": "Röbert 🐕", "species": "dog" },
{ "name": "42", "species": "fish" }
]Every item in the array becomes a full request cycle — POST, GET, PUT, DELETE — with zero extra code. The loop does the work, you focus on the data.
Shared helpers
Put reusable functions in helpers/ and import them in your request files. The compiler converts them to Postman's eval pattern automatically.
// helpers/validate.js
export function validatePet(pet) {
pm.expect(pet.name).to.be.a('string');
pm.expect(pet.species).to.be.a('string');
}// requests/read-pets.js
import { validatePet } from '../helpers/validate.js';
export function postResponse(pet) {
const petFromDb = pm.response.json();
pm.test('Pet has valid fields', () => validatePet(petFromDb));
}Helpers are only included in the scripts that actually use them.
Collection configuration
collection.js defines the collection name, request order, and optional collection-level scripts.
import getAllPets from './requests/get-all-pets.js';
import createPets from './requests/create-pets.js';
import readPets from './requests/read-pets.js';
export const name = 'PetTest';
export const order = [getAllPets, createPets, readPets];The order array determines execution order. If omitted, import order is used.
You can also add collection-level scripts that run before/after every request:
export const name = 'PetTest';
export function preRequest() {
pm.variables.set('baseUrl', 'http://localhost:4117');
}TypeScript support
post-they supports TypeScript out of the box. Write your source files as .ts — the compiler strips types automatically using esbuild before building the collection. Postman's sandbox runs plain JavaScript, so types exist only at authoring time.
// types.ts
export interface Pet {
name: string;
species: string;
id?: string;
}// requests/create-pets.ts
import type { Pet } from '../types.js';
import pets from '../data/pets.json' with { type: 'json' };
export { pets as data };
export default {
method: 'POST',
url: '{{baseUrl}}/pets',
body: { name: '{{petName}}', species: '{{petSpecies}}' }
};
export function preRequest(pet: Pet): void {
pm.variables.set('petName', pet.name);
pm.variables.set('petSpecies', pet.species);
}Add a tsconfig.json in your source folder to get IntelliSense for the pm global:
{
"compilerOptions": {
"module": "ESNext",
"moduleResolution": "bundler",
"lib": ["ESNext"],
"resolveJsonModule": true
},
"include": ["**/*.ts", "../node_modules/post-they/lib/**/*.d.ts"]
}Note: use import type for type-only imports — they get stripped cleanly and don't generate any runtime code.
Importing existing collections
Convert a Postman collection to post-they source files:
post-they import my-collection.jsonThis creates a faithful 1:1 conversion of your collection. The original scripts are preserved as-is — you can then refactor to use post-they features like data loops and helper imports at your own pace.
Try it
When you run npx post-they init, a full working CRUD example is placed in src-example/ alongside your project. To run it:
npx post-they exampleThis starts a local json-server, compiles the example, runs 242 assertions across a full create-read-update-read-delete cycle with 20 pets, and shuts down the server. Browse src-example/ to see how it's written.
CLI reference
| Command | Description |
|---------|-------------|
| npx post-they init [folder] [--ts] | Create a new project (default folder: src) |
| npx post-they build [src-dir] | Compile src/ to collection.json |
| npx post-they build [src-dir] -o out.json | Compile to a specific output file |
| npx post-they run [src-dir] | Compile and run tests |
| npx post-they run [src-dir] --bail | Stop on first test failure |
| npx post-they import <file> [out-dir] | Import a Postman collection |
| npx post-they example | Run the full CRUD example |
| npx post-they serve | Start json-server for testing |
| npx post-they reset-db | Reset db.json to fresh state |
How it works
post-they is a compiler. Your source files are never executed directly — they're parsed and transformed into a standard Postman collection.json file.
| Source | Compiles to |
|--------|-------------|
| export default { method, url, body } | item.request |
| export function preRequest() {} | event[listen=prerequest] |
| export function postResponse() {} | event[listen=test] |
| export { data } | Counter + setNextRequest loop |
| import { fn } from './helpers/...' | eval(pm.variables.get(...)) pattern |
| import data from './data/....json' | pm.variables.set(...) in collection pre-request |
The output is a standard collection that runs in Newman, Postman, or anything else that supports the Postman Collection v2.1 format.
License
MIT
