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

enders-sync

v0.2.5

Published

A Fullstack RPC library connecting your backend to your frontend seemlessly over a REST API

Readme

Enders-Sync

Enders (Backenders + Frontenders) Sync

A zero-boilerplate RPC (Remote Procedure Call) Fullstack library for Express.js that makes calling server functions from the client feel like calling local functions.

Features

  • 🏆 Fullstack: both server-side and client-side libraries
  • 🚀 Zero Boilerplate: Call server functions directly without writing fetch code
  • 🔒 Built-in Authentication: Cookie-based auth with customizable validators
  • 🎯 Type-Safe: Full TypeScript support
  • 🪶 Lightweight: Minimal dependencies
  • 🔄 Promise-Based: Works seamlessly with async/await

Table of Content

Installation

Go Back

on the server:

npm install enders-sync

on the client:

npm install enders-sync-client

Quick Start

Go Back

Server Setup

Go Back

import express from 'express';
import { createRPC } from 'enders-sync';

const app = express();
app.use(express.json());

// Create a public RPC endpoint (no authentication required)
const publicRPC = createRPC(app, '/api/public', (req) => ({
  success: true,
  metadata: { auth: { role: 'public' } }
}));

// Register your functions
publicRPC.add(function getUser(metadata, userId) {
  return { id: userId, name: 'John Doe', email: '[email protected]' };
});

publicRPC.add(function calculateSum(metadata, a, b) {
  return a + b;
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

Client Setup

Go Back

api.js:

import { RPC } from 'enders-sync-client';

// Create RPC client instance
export const api = new RPC('/api/public');

// Load available functions (call once on app initialization)
api.load('getUser', 'calculateSum');

// Now call server functions as if they were local!
const user = await api.getUser(123);
console.log(user); // { id: 123, name: 'John Doe', email: '[email protected]' }

const sum = await api.calculateSum(5, 10);
console.log(sum); // 15

App.jsx:

React Example

Go Back

import { useEffect, useState } from 'react';
import { api } from './api';

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    api.getUser(userId)
      .then(setUser)
      .catch(console.error)
      .finally(() => setLoading(false));
  }, [userId]);

  if (loading) return <div>Loading...</div>;
  return <div>{user.name}</div>;
}

Next JS example

Go Back

// app/users/[id]/page.js
import { api } from './api';

export default async function UserProfile({ params }) {
  const user = await api.getUser(params.id);
  
  return (
    <div>
      <h1>{user.name}</h1>
      {/* More user data */}
    </div>
  );
}

Authentication

Go Back

The validator function receives the full Express Request object, allowing you to access cookies, headers, query parameters, and more for authentication and validation.

import express from 'express';
import jwt from 'jsonwebtoken';
import { createRPC } from 'enders-sync';

const app = express();
app.use(express.json());

// Create a validator for your Auth and access control
function authUser(req) {
  try {
    // Access cookies (automatically parsed by enders-sync)
    const token = req.cookies.auth_token;
    
    if (!token) {
      return { success: false };
    }
    
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    
    return {
      success: true,
      metadata: { 
        auth: {
          userId: decoded.userId,
          role: decoded.role 
        }
      }
    };
  } catch (error) {
    return { success: false };
  }
}

const authenticatedRPC = createRPC(
  app, 
  '/api/user',
  authUser
);

// Access auth metadata in your functions
authenticatedRPC.add(function getMyProfile(metadata) {
  const userId = metadata.auth.userId;
  // Fetch user profile using authenticated userId
  return { id: userId, name: 'Current User' };
});

Accessing Request and Response

Go Back

The metadata object passed to your RPC handlers includes the Express req and res objects, giving you full control when needed:

publicRPC.add(function specialFunction(metadata, data) {
  // Access the Express request object
  const userAgent = metadata.req.headers['user-agent'];
  const clientIp = metadata.req.ip;
  
  // Access auth data
  const userId = metadata.auth.userId;
  
  // You can even manipulate the response
  metadata.res.setHeader('X-Custom-Header', 'value');
  
  return { processed: data };
});

Using Express Middleware

let's taking adding a rate limiter as an example

import rateLimit from 'express-rate-limit'; // import rate limiting library here here

import express from 'express';
import { createRPC } from 'enders-sync';


// setting up rate limiter
const limiter = rateLimit({
  windowMs: 60 * 1000, // 1 minute
  max: 10, // limit each IP to 10 requests per windowMs
  message: { error: 'Too many requests, slow down!' }
});

const app = express();
app.use(express.json());

// using middleware for the whole RPC path
app.use('/api/public', limiter);

// Create a public RPC endpoint (no authentication required)
const publicRPC = createRPC(app, '/api/public', (req) => ({
  success: true,
  metadata: { auth: { role: 'public' } }
}));

Security Considerations

Go Back

  • Always validate and sanitize RPC function inputs
  • Use different validators for different permission levels
  • Consider rate limiting for public endpoints
  • The auth metadata is trusted - ensure your validator is secure
  • Validator receives full Express Request object - be cautious about what you expose
  • Cookie parsing is handled automatically by the library

Multiple RPC Endpoints

Go Back

// Public API (no auth)
const publicRPC = createRPC(app, '/api/public', (req) => ({
  success: true,
  metadata: { auth: {} }
}));

// User API (requires authentication)
const userRPC = createRPC(app, '/api/user', validateUserToken);

// Admin API (requires admin role)
const adminRPC = createRPC(app, '/api/admin', validateAdminToken);

Client:

import { RPC } from "enders-sync-client"

export const publicAPI = new RPC('/api/public');
export const userAPI = new RPC('/api/user');
export const adminAPI = new RPC('/api/admin');

// define all RPC methods
publicAPI.load('getUser', 'calculateSum');
userAPI.load('getUserProfile');
adminAPI.load('getAdminProfile');

API Reference

Go Back

Server API

Go Back

createRPC(app, path, validator)

Go Back

Creates an RPC endpoint on your Express app.

Parameters:

  • app (Express): Your Express application instance
  • path (string): Base path for the RPC endpoint (e.g., /api/public)
  • validator (Validator): Authentication validator function

Returns: RPC instance

Validator Function:

type Validator = (req: Request) => {
  success: boolean;
  metadata?: {
    auth: Record<string, string | number>;
  };
}

The validator receives the full Express Request object with cookies already parsed. Return success: true with optional metadata to allow the request, or success: false to reject it.

RPC.add(functionHandler, optionalName?)

Go Back

Registers a function to be callable via RPC.

Parameters:

  • functionHandler (Function): The function to register
  • optionalName (string, optional): Custom name for the function (defaults to function.name)

Requirements:

  • Function must be a named function (not arrow function) unless you provide optionalName
  • First parameter must be metadata (contains auth, req, and res)
  • Remaining parameters are the RPC call arguments
// Using function name
rpc.add(function myFunction(metadata, param1, param2) {
  // Your logic here
  return result;
});

// Using custom name
const handler = function(metadata, param1) {
  return result;
};
rpc.add(handler, 'customName');

RPC.dump()

Go Back

Returns an array of all registered function names.

const functions = rpc.dump();
console.log(functions); // ['getUser', 'calculateSum', ...]

Client API

Go Back

new RPC(url)

Go Back

Creates a new RPC client instance.

Parameters:

  • url (string): Base URL of the RPC endpoint (e.g., /api/public)

rpc.load( ...methods )

Go Back

declares the specified RPC functions from the server. Must be called before using any remote functions.

Parameters:

  • methods (string[]): List of function names to load

rpc.call(name, params)

Go Back

Manually call an RPC function (usually not needed - use auto-generated methods instead).

Parameters:

  • name (string): Function name
  • params (Array): Function parameters

Returns: Promise<any>

Endpoints

Go Back

When you create an RPC endpoint at /api/public, one route is automatically created:

  • POST /api/public/call - Executes RPC calls

Error Handling

Go Back

Server-Side Errors

Go Back

publicRPC.add(function riskyOperation(metadata, data) {
  if (!data) {
    throw new Error('Data is required');
  }
  // Process data
  return result;
});

Client-Side Error Handling

Go Back

try {
  const result = await api.riskyOperation(null);
} catch (error) {
  console.error('RPC Error:', error.message);
  // Handle error appropriately
}

// Or with promises
api.riskyOperation(data)
  .then(result => console.log('Success:', result))
  .catch(error => console.error('Error:', error));

TypeScript Support

Go Back

Server-Side Types

Go Back

import { type Metadata, type RPCHandler } from 'enders-sync';
import { type Request } from 'express';

interface User {
  id: number;
  name: string;
  email: string;
}

const getUser: RPCHandler = function(
  metadata: Metadata,
  userId: number
): User {
  return {
    id: userId,
    name: 'John Doe',
    email: '[email protected]'
  };
};

// Custom validator with types
const myValidator = (req: Request): ValidatorReturn => {
  const token = req.cookies.token;
  
  if (!token) {
    return { success: false };
  }
  
  return {
    success: true,
    metadata: {
      auth: {
        userId: 123,
        role: 'admin'
      }
    }
  };
};

// Register the function
publicRPC.add(getUser);

Client-Side Types

Go Back

import { RPC } from 'enders-sync-client';

interface User {
  id: number;
  name: string;
  email: string;
}

export interface PublicAPI {
  getUser(userId: number): Promise<User>;
  calculateSum(a: number, b: number): Promise<number>;
}

export const public_api = new RPC('/api/public') as unknown as PublicAPI;
public_api.load('getUser', 'calculateSum');

// Now you get full type safety!
const user: User = await public_api.getUser(123);

Best Practices

Go Back

  1. Initialize once: Call api.load( ...methods ) to declare available RPC methods globally
  2. Error handling: Always handle errors from RPC calls
  3. Named functions: Use named functions (not arrow functions) for RPC handlers, or provide custom names
  4. Validation: Validate input parameters in your RPC functions
  5. Authentication: Use different RPC endpoints for different permission levels
  6. Async operations: RPC handlers can be async functions
  7. Metadata structure: Always structure your validator metadata with an auth property
  8. Cookie parsing: Cookies are automatically parsed - access them via req.cookies

Example: Complete App

Go Back

server.js:

import express from 'express';
import { createRPC } from 'enders-sync';

const app = express();
app.use(express.json());

const publicRPC = createRPC(app, '/api/public', (req) => ({
  success: true,
  metadata: { auth: {} }
}));

// Database mock
const users = [
  { id: 1, name: 'Alice', email: '[email protected]' },
  { id: 2, name: 'Bob', email: '[email protected]' }
];

publicRPC.add(function getUsers(metadata) {
  return users;
});

publicRPC.add(function getUserById(metadata, id) {
  const user = users.find(u => u.id === id);
  if (!user) throw new Error('User not found');
  return user;
});

publicRPC.add(async function searchUsers(metadata, query) {
  // Simulate async database query
  await new Promise(resolve => setTimeout(resolve, 100));
  return users.filter(u => 
    u.name.toLowerCase().includes(query.toLowerCase())
  );
});

// Example with request/response access
publicRPC.add(function getClientInfo(metadata) {
  return {
    ip: metadata.req.ip,
    userAgent: metadata.req.headers['user-agent']
  };
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

api.js:

import { RPC } from 'enders-sync-client';

export const api = new RPC('/api/public');

app.js:

import { api } from './api.js';

// Initialize
api.load('getUsers', 'getUserById', 'searchUsers', 'getClientInfo');

// Use anywhere in your app
const users = await api.getUsers();
console.log(users);

const alice = await api.getUserById(1);
console.log(alice);

const results = await api.searchUsers('bob');
console.log(results);

const clientInfo = await api.getClientInfo();
console.log(clientInfo);

License

Go Back

MIT © Hussein Layth Al-Madhachi

Contributing

Go Back

Contributions are welcome! Please feel free to submit a Pull Request.