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-client

v0.2.2

Published

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

Readme

Enders-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
  • 📡 Auto-Discovery: Client automatically discovers available server functions
  • 🎯 Type-Safe: Full TypeScript support
  • 🪶 Lightweight: No dependencies except Express
  • 🔄 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 { useExpressRPC } from 'enders-sync';

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

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

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

publicRPC.add(function calculateSum(auth_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>;
}

Authentication

Go Back

Custom Validator

Go Back

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

const app = express();


// create a validator for your Auth and access control
function authUser(cookie) {
    try {
      const token = cookie; // or parse from cookie string
      const decoded = jwt.verify(token, process.env.JWT_SECRET);
      
      return {
        success: true,
        metadata: { 
          userId: decoded.userId,
          role: decoded.role 
        }
      };
    } catch (error) {
      return { success: false };
    }
}


const authenticatedRPC = useExpressRPC (
    app, 
    '/api/user',
    authUser,
  'auth_token' // custom cookie key (default: 'token')
);


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

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

Multiple RPC Endpoints

Go Back

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

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

// Admin API (requires admin role)
const adminRPC = useExpressRPC(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');

// Load all APIs
publicAPI.load("getUser", "calculateSum"),
userAPI.load("getMyProfile"),
adminAPI.load("getAdminProfile")

API Reference

Go Back

Server API

Go Back

useExpressRPC(app, path, validator, cookieKey?)

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 (Function): Authentication validator function
  • cookieKey (string, optional): Cookie key to extract auth token from (default: 'token')

Returns: RPC instance

Validator Function:

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

RPC.add(functionHandler)

Go Back

Registers a function to be callable via RPC.

Requirements:

  • Function must be a named function (not arrow function)
  • First parameter must be auth_metadata
  • Remaining parameters are the RPC call arguments
rpc.add(function myFunction(auth_metadata, param1, param2) {
  // Your logic here
  return result;
});

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)

await rpc.load()

Go Back

Discovers and loads all available RPC functions from the server. Must be called before using any remote functions.

Returns: Promise<void>

await 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, two routes are automatically created:

  • GET /api/public/discover - Returns list of available functions
  • POST /api/public/call - Executes RPC calls

Error Handling

Go Back

Server-Side Errors

Go Back

publicRPC.add(function riskyOperation(auth_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 AuthMetadata } from 'enders-sync';

interface User{
    id:
}

function getUser(
  auth_metadata: AuthMetadata
): User {
  return {
    id: auth_metadata.id! , // members are validated by the validator
    name: 'John Doe',
    email: '[email protected]'
  };
};


// ...


// then you register the backend function
publicRPC.add(getUser);

Client-Side Types

Go Back

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

export interface PublicAPI {
  getUser(userId: number): Promise<{ id: number, name: string , email: string }>;
  calculateSum(a: number, b: number): Promise<number>;
}

export const public_api = new RPC('/api/public') 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 the available RPC functions globally in the RPC object
  2. Error handling: Always handle errors from RPC calls
  3. Named functions: Use named functions (not arrow functions) for RPC handlers
  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

Example: Complete App

Go Back

server.js:

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

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

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

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

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

publicRPC.add(function getUserById(auth_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(auth_metadata, query) {
  // Simulate async database query
  await new Promise(resolve => setTimeout(resolve, 100));
  return users.filter(u => 
    u.name.toLowerCase().includes(query.toLowerCase())
  );
});

app.listen(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");

// 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);

License

Go Back

MIT © Hussein Layth Al-Madhachi

Contributing

Go Back

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