Mali: Minimalistic gRPC Framework
Mali is a minimalistic, middleware-based gRPC microservice framework for Node.js, designed to simplify the creation of gRPC servers. It operates similarly to popular HTTP frameworks like Koa or Express, leveraging a context object (`ctx`) for request and response handling within a cascading middleware pattern. The current stable version is `0.47.1`. The project maintains a regular, albeit not rapid, release cadence, primarily focusing on dependency updates, performance improvements, and bug fixes, as seen in recent versions like `v0.47.0` and `v0.46.0`. Its key differentiator lies in its commitment to minimalism and a familiar middleware API for gRPC, abstracting away some of the complexities of the underlying `@grpc/grpc-js` and `@grpc/proto-loader` libraries, which are required as peer dependencies. It also ships with TypeScript types.
Common errors
-
TypeError: Mali.addService is not a function
cause Using `Mali.addService` in TypeScript with an older version that had incorrect type declarations, leading to compiler issues or runtime misinterpretations.fixUpgrade `mali` to `v0.44.3` or newer. Ensure correct method signature according to documentation if not using TypeScript. -
Error: Cannot find module '@grpc/grpc-js' or '@grpc/proto-loader'
cause Mali lists `@grpc/grpc-js` and `@grpc/proto-loader` as peer dependencies, meaning they must be installed explicitly alongside `mali`.fixRun `npm install mali @grpc/grpc-js @grpc/proto-loader` to ensure all necessary peer dependencies are installed. -
Error: 14 UNAVAILABLE: No connection established
cause The gRPC client failed to connect to the server. This could be due to the server not running, incorrect address/port, firewall issues, or network problems.fixVerify the server is running on the specified port and address. Check firewall rules. Ensure the client connection string matches the server's listening address.
Warnings
- breaking Mali `v0.47.0` officially dropped support for Node.js 14. Applications running on Node.js 14 might encounter issues or become unsupported.
- breaking In `v0.44.0`, several internal dependencies (`grpc-create-metadata`, `lodash.concat`, `lodash.forown`, `lodash.isplainobject`, `ascallback`) were removed. While primarily an internal cleanup for performance, if your project indirectly relied on these being available via Mali's dependency tree, it could cause runtime errors.
- gotcha Older versions of Mali (`<0.44.3`) had an invalid TypeScript declaration for `Mali.addService`. This could lead to compilation errors or incorrect type inference when using `addService` in TypeScript projects.
- gotcha Versions prior to `v0.44.2` contained a bug that caused namespaces with the key `type` to be omitted from the gRPC service definition. This could lead to missing services or methods if your proto schema used `type` as a namespace key.
- gotcha When sending response metadata, it was not always sent if an error occurred in versions prior to `v0.47.1`. This could cause clients to not receive expected metadata alongside error responses.
Install
-
npm install mali -
yarn add mali -
pnpm add mali
Imports
- Mali
const Mali = require('mali');import { Mali } from 'mali'; - Context
import type { Context } from 'mali'; - App
import { App } from 'mali';
Quickstart
const Mali = require('mali');
const path = require('path');
const fs = require('fs');
// A minimalistic gRPC service definition
const PROTO_CONTENT = `
syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
`;
// Write the proto content to a temporary file for Mali to load
const PROTO_PATH = path.resolve(__dirname, 'helloworld.proto');
fs.writeFileSync(PROTO_PATH, PROTO_CONTENT);
function sayHello (ctx) {
ctx.res = { message: `Hello ${ctx.req.name || 'World'}` };
console.log(`Handled SayHello for ${ctx.req.name || 'World'}`);
}
async function main () {
const app = new Mali(PROTO_PATH);
app.use({ sayHello });
const port = '0.0.0.0:50051';
app.start(port);
console.log(`gRPC server running on ${port}`);
// Basic client to test the server (optional, for demonstration)
const grpc = require('@grpc/grpc-js');
const protoLoader = require('@grpc/proto-loader');
const packageDefinition = protoLoader.loadSync(PROTO_PATH, { keepCase: true, longs: String, enums: String, defaults: true, oneofs: true });
const hello_proto = grpc.loadPackageDefinition(packageDefinition).helloworld;
const client = new hello_proto.Greeter(port, grpc.credentials.createInsecure());
client.sayHello({ name: 'Mali User' }, (err, response) => {
if (err) {
console.error('Client error:', err);
} else {
console.log('Client received:', response.message);
}
// Clean up temporary proto file and stop the server for a real test
fs.unlinkSync(PROTO_PATH);
app.shutdown();
});
}
main().catch(console.error);