ApiMocker is a free hosted mock REST API for frontend developers, students, instructors, and tutorial authors. It provides realistic relational data, working CRUD, filtering, sorting, pagination, search, delay and error simulation, and a predictable daily reset. No signup or API key is required.
- Website: https://apimocker.com
- Documentation: https://apimocker.com/docs
- API Base URL: https://api.apimocker.com
You can also explore the API with ApiProbe.
- Hosted Base URL: https://api.apimocker.com
- Responses use JSON except successful deletes, which return
204 No Content. Collection endpoints support pagination, filtering, and sorting.
- Users
- GET
https://api.apimocker.com/users - GET
https://api.apimocker.com/users/1 - GET
https://api.apimocker.com/users/1/posts - GET
https://api.apimocker.com/users/1/todos - GET
https://api.apimocker.com/users/search?q=john
- GET
- Posts
- GET
https://api.apimocker.com/posts - GET
https://api.apimocker.com/posts/1 - GET
https://api.apimocker.com/posts?userId=1&_page=1&_limit=10 - GET
https://api.apimocker.com/posts/search?q=development&_sort=id&_order=desc&_page=1&_limit=5 - GET
https://api.apimocker.com/posts/1/likes - POST
https://api.apimocker.com/posts/1/likes
- GET
- Todos
- GET
https://api.apimocker.com/todos - GET
https://api.apimocker.com/todos/1 - GET
https://api.apimocker.com/todos?completed=true&_page=1&_limit=10 - GET
https://api.apimocker.com/todos/search?q=review
- GET
- Comments
- GET
https://api.apimocker.com/comments - GET
https://api.apimocker.com/comments/1 - GET
https://api.apimocker.com/comments?email=reader@example.com&_page=1&_limit=5 - GET
https://api.apimocker.com/comments/search?q=great
- GET
- Pagination:
_page,_limit - Sorting:
_sort,_order(e.g.,_sort=title&_order=asc) - Text search:
field_like(e.g.,title_like=web) - Response delay (simulate latency):
_delay=2000
- GET
https://api.apimocker.com/health
- Full CRUD Operations - Create, Read, Update, Delete for users, posts, todos, and comments
- PATCH Method Support - Partial updates for users, posts, todos, and comments
- Advanced Filtering -
_like,_sort,_order, andX-Total-Counton top-level collections - Per-Resource Search - Dedicated
/searchendpoints on users, posts, todos, and comments - Response Delay Simulation -
_delayparameter for testing loading states - Error Simulation - Dedicated endpoints for testing error handling
- Realistic Data - 10 users, 100 posts, 200 todos, 500 comments with realistic content
- Rate Limiting - General and write-specific per-IP request limits
- Input Validation - Comprehensive validation for all endpoints
- Pagination - Built-in pagination support with
_pageand_limit - Daily Reset - Automatic database reset at midnight UTC
- Comprehensive Logging - Request/response logging with Winston
- TypeScript - Full type safety throughout the application
- Prisma ORM - Type-safe database operations
- Neon PostgreSQL - Cloud-hosted database
- Backend: Node.js, Express.js
- Language: TypeScript
- Database: PostgreSQL (Neon)
- ORM: Prisma
- Validation: express-validator
- Rate Limiting: express-rate-limit
- Logging: Winston
- Security: Helmet, CORS
- Scheduling: node-cron
- Node.js 22.12.x
- pnpm 11.9.0
- Neon PostgreSQL database
-
Clone the repository
git clone <repository-url> cd apimocker
-
Install dependencies
pnpm install
-
Set up environment variables Copy the example file and fill in your values:
cp apps/api/.env.example apps/api/.env
Then edit
apps/api/.envto set yourDATABASE_URL. The full set of supported variables:# Database DATABASE_URL="postgresql://username:password@host/database?sslmode=require" # DIRECT_URL="postgresql://username:password@host/database?sslmode=require" # Server PORT=8000 NODE_ENV=development ENABLE_ISOLATED_ENVIRONMENTS=false RESET_SCHEDULER=in_process # Rate Limiting RATE_LIMIT_WINDOW_MS=86400000 # 24 hours RATE_LIMIT_MAX_WRITES=100 # 100 writes per day per IP # Logging LOG_LEVEL=info
Prisma 7 reads its CLI connection URL from
prisma.config.ts. It usesDATABASE_URLby default. SetDIRECT_URLonly when schema commands need a separate direct database connection. -
Generate Prisma client
pnpm run db:generate
-
Push database schema
pnpm run db:push
-
Seed the database
pnpm run db:seed
-
Start the server
pnpm run dev:api
The API will be available at http://localhost:8000
http://localhost:8000
The API root (http://localhost:8000/) returns a small status payload. The Astro
frontend is developed separately with pnpm run dev:web.
-
List endpoints (e.g.
GET /posts,GET /users/:id/posts) wrap the array so pagination metadata can ride along:{ "data": [ ... ], "pagination": { "page": 1, "total": 100, ... } } -
Single-resource endpoints (e.g.
GET /posts/1,POST /posts,PUT /posts/1) return the bare resource object with no wrapper:{ "id": 1, "title": "...", "body": "...", "user": { ... } } -
DELETEreturns204 No Contentwith an empty body.
Each main resource exposes its own /search endpoint that searches the relevant
text fields (case-insensitive):
GET /users/search?q=john
GET /posts/search?q=development
GET /todos/search?q=review
GET /comments/search?q=greatParameters:
q(required): Search query_delay(optional): Response delay in milliseconds- Posts search additionally supports
_sort,_order,_page, and_limit.
Test loading states by adding delays to any request:
GET /posts?_delay=2000Test error handling with dedicated error endpoints:
GET /error/404
GET /error/500
GET /error/validation
GET /error/timeoutGET /healthResponse:
{
"status": "OK",
"timestamp": "2024-01-15T10:30:00.000Z",
"uptime": 3600
}GET /usersQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10, max: 100)
Example:
GET /users?_page=1&_limit=5Response:
{
"data": [
{
"id": 1,
"name": "John Doe",
"username": "johndoe",
"email": "john.doe@example.com",
"phone": "+1-555-0123",
"website": "https://johndoe.dev",
"address": {
"street": "123 Main St",
"suite": "Apt 4B",
"city": "New York",
"zipcode": "10001",
"geo": { "lat": "40.7128", "lng": "-74.0060" }
},
"company": {
"name": "Tech Solutions Inc",
"catchPhrase": "Innovating the future",
"bs": "harness real-time e-markets"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": 10,
"totalPages": 2,
"hasNext": true,
"hasPrev": false
}
}GET /users/:idExample:
GET /users/1POST /users
Content-Type: application/jsonRequest Body:
{
"name": "New User",
"username": "newuser",
"email": "newuser@example.com",
"phone": "+1-555-0123",
"website": "https://newuser.com",
"address": {
"street": "123 Main St",
"suite": "Apt 4B",
"city": "New York",
"zipcode": "10001",
"geo": { "lat": "40.7128", "lng": "-74.0060" }
},
"company": {
"name": "Company Name",
"catchPhrase": "Company catchphrase",
"bs": "Company business statement"
}
}PUT /users/:id
Content-Type: application/jsonExample:
PUT /users/1
{
"name": "Updated Name",
"username": "updateduser",
"email": "updated@example.com"
}Use PATCH /users/1 when sending only the fields that should change.
DELETE /users/:idExample:
DELETE /users/1GET /users/:id/postsQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10)
Example:
GET /users/1/posts?_page=1&_limit=5GET /users/:id/todosQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10)
Example:
GET /users/1/todos?_page=1&_limit=5GET /postsQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10)userId(optional): Filter by user ID
Example:
GET /posts?userId=1&_page=1&_limit=5Response:
{
"data": [
{
"id": 1,
"title": "Getting Started with Modern Web Development",
"body": "This is a comprehensive article about getting started with modern web development...",
"userId": 1,
"user": {
"id": 1,
"name": "John Doe",
"username": "johndoe",
"email": "john.doe@example.com"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": 100,
"totalPages": 20,
"hasNext": true,
"hasPrev": false
}
}GET /posts/:idPOST /posts
Content-Type: application/jsonRequest Body:
{
"title": "New Post Title",
"body": "This is the content of the new post..."
}Note: userId is optional and defaults to user ID 1 if not provided.
PUT /posts/:id
Content-Type: application/jsonDELETE /posts/:idGET /posts/:id/likesExample:
GET /posts/1/likesResponse:
{
"postId": 1,
"likes": 42
}POST /posts/:id/likes
Content-Type: application/jsonRequest Body (optional):
{
"userId": 1
}Note: userId is optional. Omit it for anonymous likes.
Response:
{
"message": "Like added successfully",
"like": {
"id": 123,
"postId": 1,
"userId": 1,
"createdAt": "2024-01-15T10:30:00.000Z"
},
"totalLikes": 43
}GET /todosQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10)userId(optional): Filter by user IDcompleted(optional): Filter by completion status (true/false)
Example:
GET /todos?completed=true&userId=1&_page=1&_limit=5Response:
{
"data": [
{
"id": 1,
"title": "Review pull requests",
"completed": true,
"userId": 1,
"user": {
"id": 1,
"name": "John Doe",
"username": "johndoe",
"email": "john.doe@example.com"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": 200,
"totalPages": 40,
"hasNext": true,
"hasPrev": false
}
}GET /todos/:idPOST /todos
Content-Type: application/jsonRequest Body:
{
"title": "New Todo Item",
"description": "Optional description for the todo item",
"completed": false
}Note: userId and description are optional. userId defaults to user ID 1 if not provided.
PUT /todos/:id
Content-Type: application/jsonDELETE /todos/:idGET /commentsQuery Parameters:
_page(optional): Page number (default: 1)_limit(optional): Items per page (default: 10)email(optional): Filter by exact email addressname_like(optional): Search by commenter nameemail_like(optional): Search by email
Example:
GET /comments?email=reader@example.com&_page=1&_limit=5Response:
{
"data": [
{
"id": 1,
"name": "John Doe",
"email": "john.doe@example.com",
"body": "Great article! This really helped me understand the concepts better.",
"postId": 1,
"post": {
"id": 1,
"title": "Getting Started with Modern Web Development"
},
"createdAt": "2024-01-15T10:30:00.000Z",
"updatedAt": "2024-01-15T10:30:00.000Z"
}
],
"pagination": {
"page": 1,
"limit": 5,
"total": 500,
"totalPages": 100,
"hasNext": true,
"hasPrev": false
}
}GET /comments/:idPOST /comments
Content-Type: application/jsonRequest Body:
{
"name": "Commenter Name",
"email": "commenter@example.com",
"body": "This is a comment on the post",
"postId": 1
}PUT /comments/:id
Content-Type: application/jsonDELETE /comments/:idApiMocker supports powerful filtering, sorting, and querying capabilities:
_pageorpage: Page number (default: 1)_limitorlimit: Items per page (default: 10, max: 100)
_sort: Field to sort by (e.g.,title,id,createdAt)_order: Sort order (ascordesc)
field_like: Partial text matching (case-insensitive)title_like=web- Search posts with "web" in titlename_like=john- Search users with "john" in namebody_like=development- Search posts with "development" in body
X-Total-Count: Total number of items (for pagination)
Basic pagination:
GET /posts?_page=2&_limit=5Sorting:
GET /posts?_sort=title&_order=ascText search:
GET /posts?title_like=developmentCombined filtering:
GET /todos?completed=true&userId=1&_sort=title&_order=desc&_page=1&_limit=10With delay simulation:
GET /posts?title_like=web&_delay=2000ApiMocker applies two per-IP rate limits to prevent abuse while allowing legitimate usage:
- Limit: 1,000 requests per IP address
- Window: Rolling 15 minutes
- Applies to: All public API methods, including reads and writes
- Limit: 100 combined POST, PUT, PATCH, and DELETE requests per IP address
- Window: Rolling 24 hours by default, configurable through
RATE_LIMIT_WINDOW_MS - Reset: 24 hours after the first counted write in the current window
Both rate-limit counters are stored in the API process and are not shared across multiple instances. Restarting the API starts new windows. The midnight UTC database reset is separate and does not reset either running-process counter.
When the general request limit is exceeded, the API returns:
{
"error": "Too Many Requests",
"message": "Too many requests from this IP, please try again later."
}When the additional write limit is exceeded, the response includes the end of the rolling write window:
{
"error": "Too Many Requests",
"message": "Write limit exceeded. Maximum 100 write operations per day per IP.",
"resetTime": "2026-07-27T14:30:00.000Z"
}HTTP Status: 429 Too Many Requests
The general limiter includes these standard headers on public API responses:
RateLimit-Policy: Limit and window policy, such as1000;w=900RateLimit-Limit: Maximum requests allowed in the general windowRateLimit-Remaining: Remaining requests in the general windowRateLimit-Reset: Seconds until the general window resetsRetry-After: Seconds to wait, included when the general limit returns 429
These headers describe the 1,000-request general quota. The additional write limiter does not publish separate quota headers. Its 429 response reports the write-window reset time in the JSON body.
Browser JavaScript can read X-Total-Count, but the current CORS configuration
does not expose the RateLimit-* headers. Server clients and API tools can read
them directly. Browser clients should always handle the 429 status and JSON body.
Write endpoints validate resource fields as follows:
- name: Required, 1-100 characters
- username: Required, 3-50 characters, alphanumeric + underscore only
- email: Required, valid email format
- phone: Optional, valid phone number
- website: Optional, valid URL
- address: Optional, must be an object
- company: Optional, must be an object
- title: Required, 1-200 characters
- body: Required, 1-5000 characters
- userId: Optional, positive integer (defaults to user ID 1 if not provided)
- title: Required, 1-200 characters
- description: Optional, 1-1000 characters
- completed: Optional, boolean value
- userId: Optional, positive integer (defaults to user ID 1 if not provided)
- name: Required, 1-100 characters
- email: Required, valid email format
- body: Required, 1-1000 characters
- postId: Required, positive integer
- userId: Optional, positive integer. Omit it for an anonymous like.
{
"error": "Validation Error",
"message": "Invalid input data",
"details": [
{
"type": "field",
"value": "",
"msg": "Name is required and must be between 1 and 100 characters",
"path": "name",
"location": "body"
}
]
}- Shared API JSON request bodies are limited to 10 MB.
- In-development isolated-environment request bodies are limited to 64 KB.
- Requests above the applicable limit return
413 Payload Too Large.
The database automatically resets at midnight UTC every day:
- All existing data is cleared
- Fresh seed data is inserted
- 10 users, 100 posts, 200 todos, and 500 comments are created
- Reset includes realistic data with proper relationships
This ensures a consistent testing environment and prevents data accumulation.
Set RESET_SCHEDULER=in_process to run the midnight reset inside the API
process. Use external when a platform cron service runs pnpm run db:reset,
or disabled when no scheduled reset should run.
ApiMocker uses Winston for comprehensive logging:
logs/error.log: Error-level logs onlylogs/combined.log: All logs
- Request Details: Method, URL, IP, User-Agent, timestamp
- Response Details: Status code, duration, content length
- Error Details: Stack traces, error messages
- Database Operations: Connection status, seeding events
{
"level": "info",
"message": "Request completed",
"method": "GET",
"url": "/users",
"statusCode": 200,
"duration": "45ms",
"timestamp": "2024-01-15T10:30:00.000Z",
"service": "apimocker"
}# Development
pnpm run dev # Start both development servers
pnpm run dev:api # Start the API development server
pnpm run dev:web # Start the Astro development server
pnpm run build # Build both workspace apps
pnpm run build:api # Build the API
pnpm run build:web # Build the Astro app
pnpm run typecheck # Type-check both workspace apps
pnpm run start # Start the built API
pnpm run start:api # Start the built API
pnpm run start:web # Start the built Astro Node server
# Database
pnpm run db:generate # Generate Prisma client
pnpm run db:migrate # Apply tracked database migrations
pnpm run db:push # Push schema to database
pnpm run db:seed # Seed database with sample data
pnpm run db:reset # Reset and reseed database
pnpm run db:studio # Open Prisma Studio
# Testing
pnpm test # Run all API tests
pnpm run test:watch # Run API tests in watch mode
pnpm run test:coverage # Run API tests with coverage
pnpm run test:integration # Run only integration tests
pnpm run test:unit # Run only unit tests
pnpm run test:environment # Run isolated environment testsapimocker/
βββ apps/
β βββ api/
β β βββ prisma/ # Database schema and migrations
β β βββ public/ # Preserved legacy homepage source
β β βββ src/ # Express API source
β β βββ tests/ # API test suites and guide
β β βββ .env.example # API environment template
β β βββ package.json
β β βββ prisma.config.ts
β βββ web/
β βββ public/ # Astro static assets
β βββ src/ # Astro pages and components
β βββ astro.config.mjs
β βββ package.json
βββ package.json # Workspace scripts
βββ pnpm-workspace.yaml
βββ README.md
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL connection string | Required |
TEST_DATABASE_URL |
Dedicated database used only by the default Jest suite | Required for database tests |
DIRECT_URL |
Optional direct URL for Prisma CLI commands | DATABASE_URL |
PORT |
Server port | 8000 |
NODE_ENV |
Environment (development/production) | development |
ENABLE_ISOLATED_ENVIRONMENTS |
Development flag for unfinished isolated environment routes | false |
RESET_SCHEDULER |
Reset mode: in_process, external, or disabled | in_process |
RATE_LIMIT_WINDOW_MS |
Rate limit window in milliseconds | 86400000 (24h) |
RATE_LIMIT_MAX_WRITES |
Maximum write operations per day per IP | 100 |
LOG_LEVEL |
Logging level (error, warn, info, debug) | info |
The shared API uses five models:
- User: Personal information, contact details, address, company
- Post: Blog posts with title, body, and user relationship
- Todo: Task items with title, completion status, and user relationship
- Comment: Comments on posts with name, email, and body
- Like: Likes on posts (optional userId for anonymous likes)
All models include timestamps and proper foreign key relationships, with cascade deletes from parents to children.
The in-development isolated environment work adds ApiEnvironment for
credentials and quotas, plus EnvironmentCollection for private JSON resource
snapshots. This product is not publicly available. These models are not touched
by the shared API's daily reset.
Production runs as separate API and web services on Railway from this pnpm
workspace. The API builds with pnpm run build:api and starts with
pnpm run start:api. The Astro app builds with pnpm run build:web and starts
with pnpm run start:web using the Node adapter in standalone mode.
ApiMocker includes a comprehensive testing suite with both integration and unit tests.
-
Set up test environment:
# Create test database createdb apimocker_test # Set up test environment variables cp apps/api/.env.example apps/api/.env.test # Edit TEST_DATABASE_URL in apps/api/.env.test to use a dedicated test database
-
Run tests:
# Run all tests pnpm test # Run with coverage pnpm run test:coverage # Run specific test types pnpm run test:integration pnpm run test:unit
- Integration Tests: Full API endpoint testing with real database operations
- Unit Tests: Individual component testing with mocked dependencies
- Manual Testing: Example scripts for manual API testing
The test suite covers:
- β All CRUD operations for Users, Posts, and Todos
- β Pagination and filtering
- β Input validation and error handling
- β Rate limiting enforcement
- β Database relationships
- β Edge cases and boundary conditions
For detailed testing information, see apps/api/tests/README.md.
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
This project is licensed under the MIT License.
For issues, questions, or contributions:
- Create an issue on GitHub
- Check the documentation
- Review the API endpoints
ApiMocker - Your reliable fake API for development and testing! π―
Not available yet: Isolated environments are still in development. There is no public beta, signup, billing, or customer provisioning at this time. The routes and tooling in this section document unfinished development work and are not part of the currently available ApiMocker service.
The shared /users, /posts, /todos, and /comments API remains free and
resets every day. The planned isolated environments will keep a private copy of
those five collections, including likes, behind an API key. Data written to one
environment will not affect the shared API or another environment.
Environment routes use this base path:
/v1/environments/:slugSend the API key with every environment request. This key is a browser-visible metering token for non-sensitive mock data, so it is not an authorization boundary for confidential information:
X-API-Key: am_env_your_keyThe four main resources support CRUD plus the common filtering, sorting, pagination, search, relationship, and post-like flows used by the shared API.
The current development contract includes these planned differences:
- Every isolated request requires an API key and limits request bodies to 64 KB.
- Authenticated requests to provisioned development environments consume persistent monthly and burst quota after payload parsing.
- Usage and reset are environment-only routes. Reset also requires the management key.
- Isolated environments do not expose
/healthor/errorroutes. - Isolated routes do not support
_delayresponse simulation. - Isolated writes discard unknown input fields, while shared Prisma-backed writes reject fields outside the resource schema.
- Isolated domain errors can use a different response envelope from shared Prisma errors.
The planned environment-only routes, currently implemented behind a disabled development flag, are:
GET /v1/environments/:slug/usage
POST /v1/environments/:slug/resetReset also requires a server-side management key:
X-Management-Key: am_mgmt_your_keyUsage is enforced with persistent monthly and per-minute counters in PostgreSQL. Each resource also has a record cap. The reset route restores the private data snapshot captured when the environment was created. The global midnight reset does not modify provisioned development environments. Keep the management key on the server and never include it in browser code.
Billing, customer self-service, and public provisioning are not available. Internal development tooling can provision and revoke test environments from the server:
pnpm run env:create -- --slug acme-course --name "Acme Course" --plan classroom
pnpm run env:revoke -- --slug acme-courseThe developer plan defaults to 25,000 monthly requests, 120 requests per
minute, and 1,000 records per resource. The classroom plan defaults to
100,000 monthly requests, 240 requests per minute, and 2,000 records per
resource. Provisioning arguments can override those limits during development
testing.
The create command copies all current global tables in ID order inside one repeatable-read transaction. It displays the API and management keys once and stores only their SHA-256 hashes.
Existing deployments that were created with prisma db push must mark the
tracked baseline as already applied before deploying the environment tables:
pnpm --filter @apimocker/api exec prisma migrate resolve --applied 20260720112500_baseline_existing_schema
pnpm run db:migrateRun these commands with DIRECT_URL pointing to the target database. A fresh
database only needs pnpm run db:migrate. Keep
ENABLE_ISOLATED_ENVIRONMENTS=false during the migration. Do not enable it in
production until the environment product is ready for release.
The routes stay unavailable while ENABLE_ISOLATED_ENVIRONMENTS is false.
That flag should remain false for the current public service. Keep any keys
created during development in a password manager or secrets service.
