npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sampathkumara/express-to-dart-endpoints-builder

v1.1.0

Published

Automatically generate Dart API clients from Express.js route definitions with JSDoc comments

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 .tsx route files

Installation

As an npm package

npm install @sampathkumara/express-to-dart-endpoints-builder

Global CLI

npm install -g @sampathkumara/express-to-dart-endpoints-builder

Local development

git clone <repository>
cd express-to-dart-endpoints-builder
npm link

Quick Start

1. Initialize configuration

express-to-dart init

This 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-dart

This 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:

  • savePath parameter for file save location
  • useServerFileName option
  • 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/:userIdgetUsersById()
  • POST /usersusers()
  • GET /posts/:postIdgetPostsById()
  • PUT /posts/:postIdputPostsById()

CLI Commands

Generate Dart Client

express-to-dart generate
# or
express-to-dart

With custom config:

express-to-dart generate --config ./config.js

Initialize Configuration

express-to-dart init

Creates endpoints-to-dart.config.js in current directory.

Help

express-to-dart --help
express-to-dart -h

Version

express-to-dart --version
express-to-dart -v

Programmatic 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/usersusers()
  • /api/users/:idusersById()
  • /api/users/:id/postsusersById_posts()
  • /api/v1/user-profiles/:id/detailsv1_userProfiles_by_id_details()

Troubleshooting

Routes not found

  1. Check that api.js exists in your routesDir
  2. Verify route definitions use router.get/post/put/delete/patch() syntax
  3. Ensure sub-routers are mounted with router.use(path, routerVariable)
  4. Check that variables are defined with const router = require(...)

Parameters not extracted

  1. Add JSDoc @var comments above routes
  2. Use explicit prefixes: req.body.field, req.query.field, req.params.field
  3. For path parameters, they should appear in the route path like :paramName
  4. Ensure parameter names are valid JavaScript identifiers

Wrong Dart types generated

  1. Check type names in @var tags (case-sensitive for custom types)
  2. Use supported types from the type mapping table
  3. For array types, use one of: string[], list<string>, array<string>
  4. For maps, use: map<string, int>

Config file not found

  1. Run express-to-dart init first
  2. Or create endpoints-to-dart.config.js manually
  3. Or use --config flag 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:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests if applicable
  5. 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 .ts and .tsx files
  • Smart file resolution for sub-routers (tries .ts, .tsx, then .js)
  • API entry point detection supports api.ts, api.tsx, or api.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