mayr_fake_api 2.0.0
mayr_fake_api: ^2.0.0 copied to clipboard
A pure Dart package that intercepts API calls and returns fake JSON responses from local files during development.
π§ͺ mayr_fake_api #
No internet? No backend? No problem.
A lightweight fake API simulator for Flutter and Dart apps β perfect for local development, prototyping, and offline testing.
With mayr_fake_api, you can simulate real REST API calls using simple JSON files β no server required.
π Overview #
mayr_fake_api
intercepts network requests (e.g. from Dio or http) and serves data from local JSON files. It works seamlessly with both pure Dart applications (loading files from the filesystem) and Flutter apps (loading from assets).
Itβs designed to make your development flow smoother, faster, and independent of backend delays.
π Features #
- πΎ Use local JSON files to simulate REST endpoints.
- π§ Supports folder-structured routing (e.g.
/api/user/profile.json
). - β‘ Supports multiple HTTP methods (
GET
,POST
,PUT
,DELETE
, etc.) using method-specific JSON naming. - β Built-in 404 simulation with customizable resolver.
- πͺ Dynamic data support with wildcards and placeholders.
- π² 50+ built-in placeholders powered by Faker: UUIDs, user data, addresses, dates, random values, and more.
- π― Parameterized placeholders:
$randomInt(min,max)
,$choose(a,b,c)
,$image(width,height)
. - π§ Custom placeholders: Define your own dynamic value generators.
- π Request context: Access
$method
,$path
,$query
in your responses. - π§± Lightweight β no dependencies on backend servers.
- π§ Ideal for demos, offline-first apps, and rapid prototyping.
- βοΈ Enable or disable fake mode at runtime
- π§ Simulate network delays
π οΈ Installation #
Add this to your pubspec.yaml
:
dependencies:
mayr_fake_api: ^2.0.0
Then import it:
import 'package:mayr_fake_api/mayr_fake_api.dart';
Migrating from v1.x? See the Migration Guide for a smooth transition.
π§© Directory Structure #
New in v2.0.0: Flat JSON structure for simplified asset management!
Instead of nested directories, use a flat structure with dot notation:
assets/
api/
user.profile.get.json
user.profile.post.json
user.profile.put.json
user.profile.delete.json
user.profile.error.json
products.get.json
products.details.get.json
This means you only need to add one directory to your pubspec.yaml
:
flutter:
assets:
- assets/api/
Backward compatibility: The package still supports the v1.x nested directory structure for seamless migration.
Each JSON file corresponds to a simulated endpoint.
V2.0 JSON Structure:
The JSON files should contain statusCode
(the HTTP status code) and body
(the actual response body):
{
"statusCode": 200,
"body": {
// ... Your response data here
},
"headers": {
// Optional: Response headers
"Content-Type": "application/json",
"X-Custom-Header": "value"
},
"cookies": {
// Optional: Response cookies
"session_id": "abc123",
"user_token": "xyz789"
}
}
Note: The headers
and cookies
fields are optional. If not provided, only the status code and body will be returned.
V1.x Compatibility: Files using data
instead of body
are still supported for backward compatibility.
π‘ How It Works #
-
When you make a GET request to
/api/user/profile
, the package looks forapi/user.profile.get.json
(v2.0) orapi/user/profile/get.json
(v1.x). -
When you make a POST request to
/api/user/profile
, it looks forapi/user.profile.post.json
(v2.0) orapi/user/profile/post.json
(v1.x). -
You can use any folder structure, e.g.:
api/user/profile/get.json api/user/profile/update/post.json
-
If the file doesnβt exist, it returns a 404 response by default β but you can override that with a custom not-found resolver.
π§± Example Usage #
For Flutter Apps #
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'package:dio/dio.dart';
import 'package:mayr_fake_api/mayr_fake_api.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final dio = Dio();
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
delay: Duration(milliseconds: 500),
enabled: kDebugMode,
debug: true, // Enable debug logging (v2.0+)
assetLoader: FlutterAssetLoader(rootBundle), // Use Flutter's asset loader
);
runApp(MyApp());
}
For Pure Dart Apps #
import 'package:dio/dio.dart';
import 'package:mayr_fake_api/mayr_fake_api.dart';
void main() async {
final dio = Dio();
await MayrFakeApi.init(
basePath: 'test/assets/api', // Use filesystem path
attachTo: dio,
delay: Duration(milliseconds: 500),
debug: true,
// assetLoader defaults to DartAssetLoader() for pure Dart
);
// Make requests as usual
final response = await dio.get('https://example.com/api/user/profile');
print(response.data);
}
Make a request #
final response = await dio.get('https://example.com/api/user/profile');
This will attempt to load api/user.profile.get.json
(v2.0) or fall back to api/user/profile/get.json
(v1.x).
π§© Handling 404 Responses #
If a requested JSON file doesnβt exist, a 404 is returned automatically.
You can customize what happens using:
MayrFakeApi.resolveNotFound((path, method) {
return MayrFakeResponse(
statusCode: 404,
data: {'error': 'No fake endpoint found for $method $path'},
);
});
π¨ Simulating Errors #
To simulate API errors, create an error.json
file in any directory.
The error file can contain:
{
"statusCode": 500,
"data": {
"code": 500,
"message": "Internal server error",
"details": "Something went wrong"
}
}
When the API detects this file, it throws a fake error response automatically.
If the statusCode
is missing or set to 200
, the error file is ignored
βοΈ Dynamic Data #
Excellent β thatβs a smart and elegant convention π
Using a reserved folder (like -
) to signal dynamic data routes keeps things clean and file-based while still flexible.
Hereβs the rewritten Dynamic Data section for your mayr_fake_api
README, following your api/user/-/profile/get.json
pattern π
βοΈ Dynamic Data #
Dynamic routes let you simulate responses that change depending on runtime input β for example, when you want user-specific data, random IDs, or timestamps β all while keeping your API file-based.
To define a dynamic endpoint, include a -
segment in your path.
For example:
api/
user/
-/
profile/
get.json
The -
folder acts as a dynamic wildcard that can represent any runtime value.
Example Usage #
If you make a request like:
final response = await dio.get('https://example.com/api/user/123/profile');
The package will automatically match and resolve:
V2.0: api/user.-.profile.get.json
V1.x: api/user/-/profile/get.json
Dynamic Placeholder Replacement #
Inside your dynamic JSON files, you can reference wildcard values and use built-in placeholders for realistic mock data:
{
"statusCode": 200,
"data": {
"id": "$uuid",
"userId": "$1",
"name": "$userFullName",
"email": "$userEmail",
"createdAt": "$timestamp"
}
}
Built-in Placeholders
Request Context:
$method
- HTTP method (GET, POST, etc.)$path
- Full request path$query
- Query parameters
Unique Identifiers:
$uuid
- UUID v4 (e.g.,550e8400-e29b-41d4-a716-446655440000
)$ulid
- ULID - Lexicographically sortable (e.g.,01HQZXVZQM7K9Q0W0R0W0R0W0R
)$id
- Random numeric ID (0-999999)$shortId
- Short alphanumeric ID (8 characters)$hash
- 40-character hexadecimal hash
Date & Time:
$timestamp
- ISO 8601 timestamp (e.g.,2025-01-15T10:30:45.123Z
)$datetime
- Same as $timestamp$now
- Same as $timestamp$date
- Current date (YYYY-MM-DD)$time
- Current time (HH:MM:SS)
User Data (via Faker):
$userId
- Random UUID for user ID$userEmail
- Random email address$username
- Random username$userFirstName
- Random first name$userLastName
- Random last name$userFullName
- Random full name$userAvatar
- Avatar image URL$userPhone
- Random phone number$userToken
- Random 64-character token
Location Data (via Faker):
$country
- Country name$countryCode
- Country code$city
- City name$state
- State name$address
- Street address$timezone
- Timezone$ipAddress
- Random IP address
Business Data (via Faker):
$currency
- Currency code (e.g., USD)$jobTitle
- Job title$companyName
- Company name$productName
- Product name$sku
- Product SKU
Random Data (via Faker):
$randomSentence
- Random lorem ipsum sentence$randomWord
- Random word$randomBool
- Random boolean (true/false)
Design/Visual:
$hexColor
- Hex color code (e.g., #FF5733)$color
- Color name$image(width,height)
- Placeholder image URL (e.g.,$image(300,200)
)
Other:
$version
- Random semantic version (e.g., 1.2.3)$statusCode
- HTTP status code (default: 200)
Parameterized Placeholders:
$randomInt(min,max)
- Random integer in range (e.g.,$randomInt(1,100)
)$randomFloat(min,max)
- Random float in range (e.g.,$randomFloat(0,1)
)$choose(a,b,c)
- Randomly picks one option (e.g.,$choose(red,green,blue)
)
Wildcard Values:
$1
,$2
,$3
, etc. - Replaced with wildcard values from URL path
Custom Placeholders
You can also define custom placeholders:
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
customPlaceholders: {
'sessionId': () => 'session-${DateTime.now().millisecondsSinceEpoch}',
'apiVersion': () => 'v2.0',
'environment': () => kDebugMode ? 'dev' : 'prod',
},
);
Then in your JSON:
{
"statusCode": 200,
"data": {
"sessionId": "$sessionId",
"apiVersion": "$apiVersion",
"environment": "$environment"
}
}
Example Usage
{
"statusCode": 200,
"data": {
"user": {
"id": "$uuid",
"name": "$userFullName",
"email": "$userEmail",
"avatar": "$userAvatar",
"phone": "$userPhone",
"address": {
"street": "$address",
"city": "$city",
"state": "$state",
"country": "$country",
"zip": "$randomInt(10000,99999)"
}
},
"metadata": {
"timestamp": "$timestamp",
"version": "$version",
"statusCode": "$statusCode",
"requestId": "$shortId"
}
}
}
Example in Action #
V2.0 Flat Structure:
api/user.-.profile.get.json
V1.x Nested Structure:
api/
user/
-/
profile/
get.json
and get.json
containing:
{
"statusCode": 200,
"data": {
"userId": "$1",
"name": "User $1",
"timestamp": "$timestamp"
}
}
A request like:
final res = await api.request('/user/42/profile', method: 'GET');
print(res.data);
will output something like:
{
"statusCode": 200,
"data": {
"id": "42",
"name": "User #42",
"fetched_at": "2025-10-06T12:34:56.789Z"
}
}
Notes #
- You can include multiple wildcards, e.g.
user.-.posts.-.get.json
(v2.0) or/api/user/-/posts/-/get.json
(v1.x). Placeholders$1
,$2
,$3
, etc. will be replaced accordingly. - If no placeholder is found, the file is returned as-is.
- Works seamlessly with all HTTP methods (
GET
,POST
, etc.). - 50+ built-in placeholders available for realistic mock data (powered by Faker)
- Supports parameterized placeholders for dynamic ranges and choices
- Custom placeholders can be defined during initialization to generate dynamic values
- All placeholders are evaluated fresh on each request for realistic data
πͺΆ Empty JSON Files #
If a JSON file exists but is empty, the API returns a 204 No Content response automatically.
π Example Directory Recap #
V2.0 Flat Structure (Recommended):
api/
user.profile.get.json -> returns profile data
user.profile.post.json -> simulate POST update
user.profile.error.json -> simulate error
products.get.json -> product listing
products.details.get.json -> single product details
user.-.profile.get.json -> dynamic user profile
V1.x Nested Structure (Still Supported):
api/
user/
profile/
get.json -> returns profile data
post.json -> simulate POST update
error.json -> simulate error
products/
get.json -> product listing
details/get.json -> single product details
π§ͺ Example Usage #
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final dio = Dio(
// ... DIO setup here
);
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
delay: Duration(milliseconds: 500),
debug: true, // Enable debug logging (v2.0+)
);
// rest of code
}
void elsewhere() async {
final response = await dio.get('api/user');
}
β FAQ (Frequently Asked Questions) #
Can this package be used with Retrofit? #
Yes! This package works seamlessly with Retrofit. Since Retrofit uses Dio under the hood, you can simply attach the fake API interceptor to your Dio instance before passing it to Retrofit.
Example:
import 'package:dio/dio.dart';
import 'package:retrofit/retrofit.dart';
import 'package:mayr_fake_api/mayr_fake_api.dart';
// Your Retrofit API interface
@RestApi(baseUrl: "https://api.example.com")
abstract class ApiClient {
factory ApiClient(Dio dio, {String baseUrl}) = _ApiClient;
@GET("/users")
Future<List<User>> getUsers();
}
void main() async {
// Create Dio instance
final dio = Dio();
// Attach fake API interceptor
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
delay: Duration(milliseconds: 500),
);
// Create Retrofit client with the Dio instance
final apiClient = ApiClient(dio);
// Make requests - they will be intercepted by the fake API!
final users = await apiClient.getUsers();
print(users);
}
Can I use this package in production? #
While technically possible, this package is designed for development and testing, not production. You should disable it in production builds:
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
enabled: kDebugMode, // Only enabled in debug mode
);
Does this work with the http package? #
Currently, this package is designed to work with Dio as it uses Dio's interceptor mechanism. Support for the http
package could be added in future versions.
How do I handle authentication tokens? #
You can include authentication tokens in your fake JSON responses using the headers
and cookies
fields:
{
"statusCode": 200,
"body": {
"user": "john@example.com"
},
"headers": {
"Authorization": "Bearer fake-token-123"
},
"cookies": {
"session_id": "abc123"
}
}
Can I use this for integration testing? #
Absolutely! This package is perfect for integration tests where you want to test your app's logic without making real network calls. Just initialize the fake API in your test setup and all Dio requests will be intercepted.
What's the difference between v1.x and v2.0 file structure? #
- v2.0 (Recommended): Uses flat structure with dot notation (e.g.,
user.profile.get.json
) - v1.x (Still supported): Uses nested directories (e.g.,
user/profile/get.json
)
Both formats are supported for backward compatibility. v2.0 is simpler as you only need to register one directory in your pubspec.yaml
.
How do I simulate slow network conditions? #
Use the delay
parameter when initializing:
await MayrFakeApi.init(
basePath: 'assets/api',
attachTo: dio,
delay: Duration(seconds: 3), // Simulate 3-second delay
);
Can I mix fake and real API calls? #
Yes! You can enable/disable the fake API at runtime:
// Disable fake API for specific calls
MayrFakeApi.disable();
await dio.get('https://real-api.com/endpoint');
// Re-enable fake API
MayrFakeApi.enable();
await dio.get('https://fake-api.com/endpoint');
π’ Additional Information #
π€ Contributing #
Contributions are highly welcome! If you have ideas for new extensions, improvements, or fixes, feel free to fork the repository and submit a pull request.
Please make sure to:
- Follow the existing coding style.
- Write tests for new features.
- Update documentation if necessary.
Let's build something amazing together!
π Reporting Issues #
If you encounter a bug, unexpected behaviour, or have feature requests:
- Open an issue on the repository.
- Provide a clear description and steps to reproduce (if it's a bug).
- Suggest improvements if you have any ideas.
Your feedback helps make the package better for everyone!
π§βπ» Author #
Building the future, one line at a time...
π Licence #
This package is licensed under the MIT License β which means you are free to use it for commercial and non-commercial projects, with proper attribution.
See the LICENSE file for more details.
MIT Β© 2025 MayR Labs
π Support #
If you find this package helpful, please consider giving it a βοΈ on GitHub β it motivates and helps the project grow!
You can also support by:
- Sharing the package with your friends, colleagues, and tech communities.
- Using it in your projects and giving feedback.
- Contributing new ideas, features, or improvements.
Every little bit of support counts! ππ