SkillAgentSearch skills...

Frodo

A code generator that turns plain old Go services into RPC-enabled (micro)services with HTTP APIs, event-driven workflows, and multi-language support.

Install / Use

/learn @bridgekit-io/Frodo
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

Frodo

Go Report Card

That's just, like, your opinion, man!

Frod is an opinionated code generator and runtime library that helps you write (micro) services/APIs that supports, both, RPC/HTTP and Event-Driven invocation. It parses the interfaces/structs/comments in your code service code to generate all of the client, server, gateway, and pub-sub communication code automatically.

You write business logic. Frodo generates the annoying copy/paste boilerplate needed to expose your service as an HTTP API as well as Pub/Sub code to create event-driven workflows across your services.

This is the spiritual successor to my original project of the same name in a different org: Frodo. It supports the RPC/HTTP related features of the original, but it addresses many shortcomings in the architecture/approach and adds Event-Driven communication with almost no extra code on your part.

Getting Started

go install github.com/bridgekit-io/frodo@latest
go get -u github.com/bridgekit-io/frodo

# You may this if you get messages about invalid dependencies...
go mod tidy

This will fetch the frodo code generation executable then add the runtime libraries that allow your services and clients to communicate with each other as a dependency to your project.

Basic Example

We're going to write a simple CalculatorService that lets you either add or subtract two numbers.

Step 1: Describe Your Service

Your first step is to write a .go file that just defines the contract for your service; the interface as well as the inputs/outputs.

// calc/calculator_service.go

package calc

import (
    "context"
)

type CalculatorService interface {
    Add(context.Context, *AddRequest) (*AddResponse, error)
    Sub(context.Context, *SubRequest) (*SubResponse, error)
}

type AddRequest struct {
    A int
    B int
}

type AddResponse struct {
    Result int
}

type SubRequest struct {
    A int
    B int
}

type SubResponse struct {
    Result int
}

One important detail is that the interface name ends with the suffix "Service". This tells Frodo that this is an actual service interface and not just some random abstraction in your code.

At this point you haven't actually defined how this service gets this work done; just which operations are available.

Step 2: Implement Your Service Logic

We actually have enough for Frodo to generate your RPC/API/Event code already, but we'll hold off for a moment. Frodo frees you up to focus on building features, so let's actually implement service - no networking, no marshaling, no status stuff, no pub/sub - just business logic to make your service behave properly.

// calc/calculator_service_handler.go

package calc

import (
    "context"
)

type CalculatorServiceHandler struct {}

func (svc CalculatorServiceHandler) Add(ctx context.Context, req *AddRequest) (*AddResponse, error) {
    result := req.A + req.B
    return &AddResponse{Result: result}, nil
}

func (svc CalculatorServiceHandler) Sub(ctx context.Context, req *SubRequest) (*SubResponse, error) {
    result := req.A - req.B
    return &SubResponse{Result: result}, nil
}

Step 3: Generate Your RPC Client and Server Code

At this point, you've just written the same code that you (hopefully) would have written even if you weren't using Frodo. Next, we want to auto-generate two things:

  • The "server" bits that allow an instance of your CalculatorService to listen for incoming requests from an either HTTP API or a published event. (We'll look at events later...)
  • A "client" struct that communicates with that API to get work done.

Just run these two commands in a terminal:

# Feed it the service interface file, not the handler.
frodo server calculator_service.go
frodo client calculator_service.go

Step 4: Run Your Calculator API

Let's fire up an HTTP server on port 9000 that makes your service available for consumption.

package main

import (
    "github.com/bridgekit-io/frodo/services"
    "github.com/bridgekit-io/frodo/services/gateways/apis"

    "github.com/your/project/calc"
    calcgen "github.com/your/project/calc/gen"
)

func main() {
    // Create your logic-only handler, then wrap it in service
    // communication bits that let it interact with the Frodo runtime.
    calcHandler := calc.CalculatorServiceHandler{}
    calcService := calcgen.CalculatorServiceServer(calcHandler)
	
    // Fire up a server that will manage our service and listen to 
    // API calls on port 9000.
    server := services.NewServer(
        services.Listen(apis.NewGateway(":9000")),
        services.Register(calcService),
    )
    server.Run(context.Background())
}

Seriously. That's the whole program.

Compile and run it, and your service/API is now ready to be consumed. We'll use the Go client we generated in just a moment, but you can try this out right now by simply using curl:

curl -d '{"A":5, "B":2}' http://localhost:9000/CalculatorService.Add
# {"Result":7}
curl -d '{"A":5, "B":2}' http://localhost:9000/CalculatorService.Sub
# {"Result":3}

Step 5: Interact With Your Calculator Service

While you can use raw HTTP to communicate with the service, let's use our auto-generated client to hide the gory details of JSON marshaling, status code translation, and other noise.

The client actually implements CalculatorService just like the server/handler does. As a result the RPC-style call will "feel" like you're executing the service work locally, when in reality the client is actually making API calls to the server running on port 9000.

package main

import (
    "context"
    "fmt"

    "github.com/your/project/calc"
    calcgen "github.com/your/project/calc/gen"
)

func main() {
    ctx := context.Background()
    client := calcgen.CalculatorServiceClient("http://localhost:9000")

    add, err := client.Add(ctx, &calc.AddRequest{A:5, B:2})
    if err != nil {
        // handle error	
    }
    fmt.Println("5 + 2 =", add.Result)

    sub, err := client.Sub(ctx, &calc.SubRequest{A:5, B:2})
    if err != nil {
        // handle error	
    }
    fmt.Println("5 - 2 =", sub.Result)
}

Compile/run this program, and you should see the following output:

5 + 2 = 7
5 - 2 = 3

That's it!

Creating a JavaScript Client

The frodo tool can actually generate a JS client that you can add to your frontend code (or React Native mobile code) to hide the complexity of making API calls to your backend service. Without any plugins or fuss, we can create a JS client of the same CalculatorService from earlier...

frodo client calc/calculator_service.go --language=js
    or
frodo client calc/calculator_service.go --language=node

This will create the file calculator_service.gen.client.js which you can include with your frontend codebase. Using it should look similar to the Go client we saw earlier:

import {CalculatorService} from 'lib/calculator_service.gen.client';

// The service client is a class that exposes all of the
// operations as 'async' functions that resolve with the
// result of the service call.
//
// All of the operations on the Go service are exposed here
// as well. The arguments to these functions are the same as
// the request struct in the Go code, and the return value
// will match the response struct from your Go service.
const service = new CalculatorService('http://localhost:9000');
const add = await service.Add({A:5, B:2});
const sub = await service.Sub({A:5, B:2});

// Should print:
// Add(5, 2) = 7
// Sub(5, 2) = 3
console.info('Add(5, 2) = ' + add.Result)
console.info('Sub(5, 2) = ' + sub.Result)

Another subtle benefit of using the generated client is that your service/method documentation follows you in the generated code. It's included in the file as JSDoc comments so your documentation should be available to your IDE even when writing your frontend code.

Node Support

Frodo uses the fetch function to make the actual HTTP requests, so if you are using Node 18+, you shouldn't need to do anything special as fetch is now in the global scope. If that's the case, ignore the next paragraph and subsequent sample code.

If you're using an older version of node or just really prefer to use the classic node-fetch package, you can supply the fetch implementation to use when constructing your client:

const fetch = require('node-fetch');

const service = new CalculatorService('http://localhost:9000', {fetch});
const add = await service.Add({A:5, B:2});
const sub = await service.Sub({A:5, B:2});

Creating a Dart/Flutter Client

Just like the JS client, Frodo can create a Dart client that you can embed in your Flutter apps so mobile frontends can consume your service.

frodo client calc/calculator_service.go --language=dart
  or
frodo client calc/calculator_service.go --language=flutter

This will create the file calculator_service.gen.client.dart. Add it to your Flutter codebase, and it behaves very similarly to the JS client.

The HttpClient from the standard dart:io package is NOT supported in Flutter web applications. To support Flutter mobile as well as web, Frodo clients uses the http package to make requests to the backend API. You'll need to add that to your pubspec for the following code to work:

import 'lib/calculator_service.gen.client.dart';

var service = CalculatorServiceClient("http://localhost:9000");
var add = await service.Add(AddRequest(A:5, B:2));
var sub = await service.Sub(SubRequest(A:5, B:2));

// Should print:
// Add(5, 2) = 7
// Sub(5, 2) = 3
print('Add(5, 2) = ${add.Result}');
print('Sub(5, 2) = ${sub.Result}');

For more examples of how to write services that let Frodo take care of the RPC/API boilerplate, take a look

Related Skills

View on GitHub
GitHub Stars4
CategoryCustomer
Updated4mo ago
Forks1

Languages

Go

Security Score

82/100

Audited on Nov 14, 2025

No findings