@sampathkumara/express-to-dart-endpoints-builder
v1.1.0
Published
Automatically generate Dart API clients from Express.js route definitions with JSDoc comments
Maintainers
Readme
Express to Dart Endpoints Builder
Automatically generate production-ready Dart API clients from your Express.js route definitions with full JSDoc support.
Features
- 🚀 Automatic code generation - Generate Dart API client methods from Express routes
- 📚 JSDoc support - Extract parameter types and documentation automatically
- 🔐 Authentication handling - Detect and include auth checks in generated methods
- 📝 Parameter parsing - Automatically extract path, query, and body parameters
- 📦 File upload/download - Support for multipart file uploads and file downloads
- ✅ Permission checks - Include permission validation in generated code
- 🎯 Route tree traversal - Recursively discover routes across all sub-routers
- 📄 Type mapping - Convert JavaScript/JSDoc types to Dart types automatically
- 💾 Configuration driven - Fully customizable via JSON config
- 🔵 TypeScript support - Works with
.js,.ts, and.tsxroute files
Installation
As an npm package
npm install @sampathkumara/express-to-dart-endpoints-builderGlobal CLI
npm install -g @sampathkumara/express-to-dart-endpoints-builderLocal development
git clone <repository>
cd express-to-dart-endpoints-builder
npm linkQuick Start
1. Initialize configuration
express-to-dart initThis creates a default endpoints-to-dart.config.js in your current directory.
2. Configure your project
Edit endpoints-to-dart.config.js:
module.exports = {
routesDir: './routes',
outputDir: './dart/lib/api',
baseUrl: 'http://localhost:3000',
apiClassName: 'Api',
classNameEndpoints: 'ServerEndpoints',
imports: [
'package:your_app/api/Api.dart',
'package:your_app/models/AppUser.dart'
]
};3. Generate Dart client
express-to-dart generate
# or simply
express-to-dartThis generates ServerEndpoints.dart in your configured output directory.
Configuration
endpoints-to-dart.config.js
All configuration options:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| routesDir | string | ../routes | Path to Express routes directory (relative to config file) |
| outputDir | string | ../../app/lib/api | Output directory for generated Dart file (relative to config file) |
| baseUrl | string | http://localhost:3000 | API base URL (for documentation) |
| apiClassName | string | Api | Name of the API class to call in generated methods |
| classNameEndpoints | string | ServerEndpoints | Name of the generated endpoints class |
| imports | array | See example | Dart import statements to include in generated file |
| includePatterns | array | ["**/*.js", "**/*.ts", "**/*.tsx"] | Glob patterns for files to include |
| excludePatterns | array | See example | Glob patterns for files to exclude |
Example Configuration
{
"routesDir": "./routes",
"outputDir": "./dart/lib/api",
"baseUrl": "http://api.example.com",
"apiClassName": "ApiClient",
"classNameEndpoints": "AppEndpoints",
"imports": [
"package:your_app/api/client.dart",
"package:your_app/models/user.dart",
"package:your_app/models/permissions.dart"
],
"includePatterns": ["**/*.js", "**/*.ts", "**/*.tsx"],
"excludePatterns": [
"**/node_modules/**",
"**/*.test.js",
"**/*.spec.js",
"**/*.test.ts",
"**/*.spec.ts"
]
}JSDoc Documentation
Parameter Documentation
Document parameters with @var tags. The generator automatically detects parameter location (path, query, body):
/**
* Get user by ID
* @var {string} userId - The user ID (automatically detected as path param)
* @var {boolean} includeDetails - Include additional user details
* @var {object} metadata - Additional metadata to save
*/
router.get('/users/:userId', async (req, res) => {
// ...
});Explicit Parameter Location
For clarity, explicitly specify parameter location:
/**
* Search users
* @var {string} req.query.q - Search query
* @var {number} req.query.limit - Results limit
* @var {object} req.body - Request body object
*/
router.post('/users/search', async (req, res) => {
// ...
});Return Type & File Downloads
Mark methods that return files:
/**
* Download user export as PDF
* @var {string} userId - User ID
* @returns {file}
*/
router.get('/users/:userId/export', async (req, res) => {
// ...
});Generated method includes:
savePathparameter for file save locationuseServerFileNameoption- Progress callback support
- Calls
Api.downloadFile()instead of regular HTTP method
Deprecation
Mark deprecated endpoints:
/**
* Get legacy user data
* @deprecated Use getUserProfile instead
*/
router.get('/api/v1/users/:id', async (req, res) => {
// ...
});Generated method will have @Deprecated() annotation in Dart.
Type Mapping
Supported Types
| JS/JSDoc Type | Dart Type |
|---------------|-----------|
| string | String |
| number | double |
| int, integer | int |
| boolean, bool | bool |
| date, datetime | DateTime |
| file | File |
| object, json, map | Map<String, dynamic> |
| array, list, [] | List<dynamic> |
| string[], list<string> | List<String> |
| map<string, int> | Map<String, int> |
| any, * | dynamic |
Examples
/**
* Complex parameter example
* @var {string[]} tags - List of tags
* @var {object} metadata - Metadata object
* @var {map<string, int>} scores - Score mapping
* @var {file} document - Document file
*/
router.post('/items', async (req, res) => {
// ...
});Generated Dart Code
ServerEndpoints Class
All endpoint methods are static and return Future<Response>:
class ServerEndpoints {
/// Get user profile by ID
/// Source: routes/users.js:12
/// Authenticated: true
/// Permissions: NsPermissions.USER_READ
static Future<Response> getUsersById({
required String userId,
required String includeDetails,
Map<String, dynamic>? data,
ApiOptions? options,
}) async {
if (!AppUser.havePermissionFor(NsPermissions.USER_READ)) {
throw Exception("Permission denied: NsPermissions.USER_READ");
}
return Api.get(
'/users/${userId}',
data: {
...queryParams,
...(data ?? {}),
},
reFreshToken: options?.reFreshToken ?? false,
cancelToken: options?.cancelToken,
enableRetry: options?.enableRetry ?? true,
skipAuth: options?.skipAuth ?? false,
maxRetries: options?.maxRetries ?? 3,
options: options?.options,
);
}
}ApiOptions Class
Control request behavior:
class ApiOptions {
final bool reFreshToken; // Force token refresh
final CancelToken? cancelToken; // Dio cancel token
final void Function(int, int)? onReceiveProgress; // Download progress
final void Function(int, int)? onSendProgress; // Upload progress
final bool enableRetry; // Enable retry logic
final bool? skipAuth; // Bypass authentication
final int maxRetries; // Retry attempts (default 3)
final Options? options; // Custom Dio options
final bool? useServerFileName; // Use server filename for downloads
final FormData? formData; // Custom FormData
}Usage Example
// Simple GET request
final response = await ServerEndpoints.getUsersById(
userId: '123',
includeDetails: true,
);
// With options and error handling
try {
final response = await ServerEndpoints.createUser(
name: 'John',
email: '[email protected]',
options: ApiOptions(
enableRetry: true,
maxRetries: 5,
),
);
} catch (e) {
print('Error: $e');
}
// File download with progress
final response = await ServerEndpoints.downloadExport(
userId: '123',
savePath: '/path/to/save',
useServerFileName: true,
options: ApiOptions(
onReceiveProgress: (received, total) {
print('Progress: ${(received / total * 100).toStringAsFixed(0)}%');
},
),
);
// File upload
final file = File('/path/to/file.pdf');
final response = await ServerEndpoints.uploadDocument(
document: file,
userId: '123',
options: ApiOptions(
onSendProgress: (sent, total) {
print('Upload: ${(sent / total * 100).toStringAsFixed(0)}%');
},
),
);Express Route Examples
Simple GET with Path Parameter
/**
* Fetch a single post by ID
* @var {string} postId - The post ID
*/
router.get('/posts/:postId', async (req, res) => {
const post = await Post.findById(req.params.postId);
res.json(post);
});Generated Dart:
static Future<Response> getPostsById({
required String postId,
Map<String, dynamic>? data,
ApiOptions? options,
}) async {
return Api.get('/posts/${postId}', ...);
}POST with Query and Body Parameters
/**
* Create a new post
* @var {string} req.query.notify - Notify followers
* @var {string} title - Post title
* @var {string} content - Post content
* @var {string[]} tags - Post tags
*/
router.post('/posts', async (req, res) => {
const post = await Post.create(req.body);
res.json(post);
});Generated Dart:
static Future<Response> posts({
required String notify,
required String title,
required String content,
required List<String> tags,
Map<String, dynamic>? data,
ApiOptions? options,
}) async {
final bodyData = <String, dynamic>{
'title': title,
'content': content,
'tags': tags,
};
final queryParams = <String, dynamic>{
'notify': notify,
};
return Api.post(
'/posts',
data: data ?? bodyData,
...
);
}File Upload
/**
* Upload a document
* @var {string} userId - User ID
* @var {file} req.body.document - Document file
*/
router.post('/users/:userId/documents', async (req, res) => {
const doc = await Document.create({
file: req.files.document,
userId: req.params.userId,
});
res.json(doc);
});Generated Dart:
static Future<Response> usersById_documents({
required String userId,
required File document,
Map<String, dynamic>? data,
ApiOptions? options,
}) async {
final fileFormData = FormData();
fileFormData.files.add(MapEntry(
'document',
await MultipartFile.fromFile(document.path,
filename: document.path.split(Platform.pathSeparator).last),
));
return Api.post(
'/users/${userId}/documents',
formData: fileFormData,
...
);
}With Authentication & Permissions
/**
* Delete a user (admin only)
* @var {string} userId - User to delete
*/
router.delete(
'/users/:userId',
authenticateToken,
PermissionsMiddleware([NsUserPermissions.ADMIN_USERS]),
async (req, res) => {
await User.destroy({where: {id: req.params.userId}});
res.json({success: true});
}
);Generated Dart:
static Future<Response> usersById({
required String userId,
Map<String, dynamic>? data,
ApiOptions? options,
}) async {
if (!AppUser.havePermissionFor(NsPermissions.ADMIN_USERS)) {
throw Exception("Permission denied: NsPermissions.ADMIN_USERS");
}
return Api.delete('/users/${userId}', ...);
}Nested Routes with Sub-routers
// routes/api.js
const usersRouter = require('./users');
const postsRouter = require('./posts');
router.use('/users', usersRouter);
router.use('/posts', postsRouter);
// routes/users.js
router.get('/:userId', (req, res) => { ... });
router.post('/', (req, res) => { ... });
// routes/posts.js
router.get('/:postId', (req, res) => { ... });
router.put('/:postId', (req, res) => { ... });Generated routes:
GET /users/:userId→getUsersById()POST /users→users()GET /posts/:postId→getPostsById()PUT /posts/:postId→putPostsById()
CLI Commands
Generate Dart Client
express-to-dart generate
# or
express-to-dartWith custom config:
express-to-dart generate --config ./config.jsInitialize Configuration
express-to-dart initCreates endpoints-to-dart.config.js in current directory.
Help
express-to-dart --help
express-to-dart -hVersion
express-to-dart --version
express-to-dart -vProgrammatic API
Use the generator in your Node.js code:
const { generate } = require('@sampathkumara/express-to-dart-endpoints-builder');
// Generate with default config
const result = generate();
if (result.success) {
console.log(`Generated ${result.count} endpoints`);
console.log(`Output: ${result.outputPath}`);
} else {
console.error(result.message);
}With custom config path:
const result = generate('./custom-config.js');Advanced Features
Custom Imports
Add custom Dart imports to generated file:
{
"imports": [
"package:your_app/api/client.dart",
"package:your_app/models/user.dart",
"package:dio/dio.dart",
"dart:convert"
]
}Exclude Test Files
{
"excludePatterns": [
"**/node_modules/**",
"**/*.test.js",
"**/*.spec.js",
"**/test/**"
]
}Nested Route Hierarchies
The generator automatically handles:
- Direct router definitions in files
- Sub-routers mounted via
router.use(path, router) - Array-based route definitions
- Authentication inheritance across nested routes
- Permission combination across route tree
Method Naming Convention
Route paths are converted to camelCase method names:
/api/users→users()/api/users/:id→usersById()/api/users/:id/posts→usersById_posts()/api/v1/user-profiles/:id/details→v1_userProfiles_by_id_details()
Troubleshooting
Routes not found
- Check that
api.jsexists in yourroutesDir - Verify route definitions use
router.get/post/put/delete/patch()syntax - Ensure sub-routers are mounted with
router.use(path, routerVariable) - Check that variables are defined with
const router = require(...)
Parameters not extracted
- Add JSDoc
@varcomments above routes - Use explicit prefixes:
req.body.field,req.query.field,req.params.field - For path parameters, they should appear in the route path like
:paramName - Ensure parameter names are valid JavaScript identifiers
Wrong Dart types generated
- Check type names in
@vartags (case-sensitive for custom types) - Use supported types from the type mapping table
- For array types, use one of:
string[],list<string>,array<string> - For maps, use:
map<string, int>
Config file not found
- Run
express-to-dart initfirst - Or create
endpoints-to-dart.config.jsmanually - Or use
--configflag to specify custom path
Performance
The generator scans your routes directory and generates Dart code in seconds, even for large projects with hundreds of endpoints.
Contributing
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests if applicable
- Submit a pull request
License
MIT
Support
For issues, questions, or suggestions:
- Open an issue on GitHub
- Check the documentation in CLAUDE.md
- Review the examples in this README
Changelog
v1.1.0
- TypeScript support - Now collects routers from
.tsand.tsxfiles - Smart file resolution for sub-routers (tries
.ts,.tsx, then.js) - API entry point detection supports
api.ts,api.tsx, orapi.js - Default include patterns updated to include TypeScript files
v1.0.0
- Initial release
- Full JSDoc support
- Authentication and permission handling
- File upload/download support
- Nested route discovery
- Type mapping for Dart
