API Gateways in Zuplo - Creating an ASCII Art API

I was approached by Adrian Machado, an employee at Zuplo, for a problem I didn't even know I had -- yet. When I jumped on the call with him to walk through their product offering, I remember thinking "Holy crap -- this is for me". You see, I'm more than happy to code up solutions for things and build systems, but there's a class of work that I REALLY don't want to have to deal with. And every single time I work on a new project/product/service that's web-facing this stuff comes up every time... And API Gateways in Zuplo I feel like make this infinitely more easy to navigate.

In this article, I'm going to share with you my experiences with onboarding my ASCII art generator onto an API Gateway in Zuplo. To cover all of the capabilities, I'll need to spread this out over several articles, but this will explain how I achieved routing traffic through the gateway!

Please note that this is a sponsored post by Zuplo. I do not promote or accept sponsorships for products or services unless I think they are awesome.


What is Zuplo?

Zuplo is an API Management platform that places an emphasis on the Developer Experience (DevEx) for building web APIs. Zuplo's goal is to build a platform to make it fast and straightforward to build awesome APIs. Directly from them:

We are trying to build a platform to make it fast and straightforward for developers to deliver a Stripe-level API experience to their users.

Zuplo

It's one thing to get the logic for an API implemented -- which is where historically I shine pretty well. But building out a fully operational API that you can have users and other services relying on isn't a trivial process -- especially once you consider monetizing it. You'll need to factor in:

  • Documentation
  • Testing
  • Deployments
  • Monitoring

Nevermind all of the other things regarding access and protection like authentication and rate limiting... The list goes on. But Zuplo aims to try and get you focused on what matters (what your API needs to do) so they can handle the rest.

What is an API Gateway?

An API Gateway is a component in many modern distributed systems, particularly -- but not exclusively -- in microservices architectures. It acts like the bouncer at the club of your backend services, managing and directing incoming API calls from clients to various backend services. I like to think of it like "the front door" -- although it does more than that.

So... What exactly does that mean?

When you have a bunch of services, each handling a specific piece of functionality (like billing, user profiles, inventory, etc.), it's not always ideal to have everyone on the outside of your system hitting them directly. And now you might ask, "Well... Why?", and that's because managing all those direct connections can get messy.

Think about handling authentication, rate limiting, and data transformation for each service individually. That's where the API Gateway comes into play. These can simplify a lot of the interactions between services as well as callers to your services on the outside. The API Gateway simplifies client interactions with your service or services -- like my front door analogy -- providing a single entry point for all client requests.

What Does Zuplo Offer for API Gateways?

While this article will not be covering all of these in detail (c'mon, I need to spread out the juicy details a little bit...), here are some of the core offerings from Zuplo with respect to API gateways:

  • API Key Management: Zuplo allows you to add API key-based authentication to your APIs rapidly -- literally in a few minutes.
  • Rate Limiting and Throttling: Zuplo's rate-limiting functionality can work great when paired with their other features, like the API keys I just mentioned. They have ready-to-go limiting based on keys or JWTs but they also allow you to customize this further in code.
  • Routing and Forwarding: With Zuplo, you can handle the request right at the edge server using the code you write in Zuplo itself, you can run a lambda, or you can forward/rewrite the request!

Yes, sure, these are all things that Zuplo didn't invent, but using these within Zuplo means you don't have to go build them from scratch -- and they make it ridiculously easy for you to use.

But what else does their platform do for you if you're developing APIs?

  • Full Policy Library: It's actually almost ridiculous how many out-of-the-box policies are provided for what you might want to do with your API. Think of these almost like a "middleware" that you can insert inbound and outbound on your API as building blocks -- which means you don't need to go build this stuff from scratch!
  • Documentation Generation: Right in the portal you're able to configure and define documentation for your APIs, but all of the heavy lifting is done for you. Simply make your changes, customize what you want in your documentation, save and it's automatically deployed to an easily consumable API documentation portal.
  • Automatic Rapid Deployments: During my demo, I was blown away by just making changes in the portal, and pressing save, and everything is instantly deployed and live. Honestly, the API developer experience feels ridiculously smooth for these features because of how simple and quick these things become.

ASCII Art Generator Via Zuplo

In the following sections, I'll walk you through how I went from my desktop C# ASCII art generator to an API endpoint on Zuplo that hits my server in the cloud! You can also watch the companion video where I explain how I got my ASCII art generator API setup with an API gateway:

Why an ASCII Art Generator?

In 2023 I wrote an article on ASCII art and how to create an ASCII art generator in C#. I thought this was a fun little project, but it ended up being one of the articles that still -- to this very day -- drives a lot of traffic to my website. Even the YouTube video, which I'll embed right after, has a thumbnail that still ranks quite high on Google!

ASCII Art Generator in C# - Thumbnail on Google Images

This project was fun to build, drove traffic to my site, and was quite self-contained with respect to the amount and complexity of code. I figured that this would be a perfect candidate to build an API for.

But back when I first started getting traction for this content, I really didn't want to be bothered with building out all of the rate-limiting, auth, etc... These are all things I know can be done when building ASP.NET core applications, but honestly, I couldn't be bothered to invest the time.

But Zuplo changes that for me because the reality is I don't have to invest much time at all! I mean this candidly: When Adrian from Zuploo got on the call with me, I didn't even realize I had a problem that Zuplo would solve until he started walking me through their offering. I realized literally while on the call that Zuplo is what could allow me to skip over the parts of API development that I truly just don't want to work on!

Initial Challenges -- Nick's Fault!

Thanks to the code being very simple for ASCII art generation, I figured I'd just port everything over directly to Zuplo! One of the very cool features of Zuplo is that I can define a route's logic RIGHT in Zuplo using Typescript:

Zuplo Route Request Handler

It's worth noting that just by doing that configuration, Zuplo will automatically go add the associated Typescript file for you:

Zuplo Route Request Handler with Typescript Code

So this is TOTALLY awesome -- especially for Typescript folks. But here's where my challenge started.

Because I'm dealing with images, I need a package that handle different image formats. In order to get this set up in Zuplo, which is very doable, I have to understand a little bit more about building things in Typescript. Directly from their documentation:

Additionally, you can also bundle custom modules inside of your own project. This process is does require some knowledge of node and npm, but it allows you to use any module or version of the module. To learn how to bundle your own modules, see the sample Custom Modules on Github.

Zuplo

So for those of you who work with Node and you're familiar with all of this stuff -- Lucky for you, this should be trivial! For me, the easiest thing was to pivot.

While it would be super cool to have my API running completely on Zuplo's edge servers, I already have machines up hosted in the cloud. It was time to turn to Azure!

Deploying A Web Service to Azure

Fortunately for me, I already had a multi-purpose API server set up in Azure that I use for several of my other projects. It's completely plugin-based, so any time I'd like to extend functionality and add new feature sets, I just add new plugins!

In this case, it was almost just as simple as copying the example code that I had from my previous article on building an ASCII art generator in C# over to a new plugin namespace in my existing web app. One of the small steps I needed to take was putting the web API in place for the first time though!

This was an interesting opportunity though because I wasn't writing an API in C# for end-users to hit... I was writing an API for Zuplo to hit! While I don't want to write a crappy API right at my origin, I figured that at least Zuplo would allow me to build transforms to my API if needed.

Here's a quick peek at what my code looks like on the server side:

using Autofac;
using Microsoft.AspNetCore.Mvc;

internal sealed class AsciiModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder
            .RegisterType<AsciiArtFromUrlRequestValidator>()
            .SingleInstance();
        builder
            .RegisterType<AsciiArtGenerator>()
            .AsImplementedInterfaces()
            .SingleInstance();
        builder
            .Register(ctx =>
            {
                var app = ctx.Resolve<WebApplication>();

                var routeGroup = app.MapGroup("ascii");

                // FIXME: correct the container ordering-issue to inject properly
                var imageSourceFactory = ctx.Resolve<IImageSourceFactory>();
                var asciiArtGenerator = ctx.Resolve<IAsciiArtGenerator>();
                var asciiArtFromUrlRequestValidator = ctx.Resolve<AsciiArtFromUrlRequestValidator>();

                routeGroup.MapPost(
                    "/url",
                    async (
                        [FromBody] AsciiArtFromUrlRequest request,
                        IHttpClientFactory httpClientFactory,
                        CancellationToken cancellationToken) =>
                    {
                        if (!asciiArtFromUrlRequestValidator.Validate(request).IsValid)
                        {
                            return Results.BadRequest();
                        }
                        
                        // FIXME: introduce further validations
                        Stream imageStream;
                        try
                        {
                            using var client = httpClientFactory.CreateClient();
                            imageStream = await client
                                .GetStreamAsync(request.ImageUrl, cancellationToken)
                                .ConfigureAwait(false);
                        }
                        catch
                        {
                            return Results.Problem();
                        }

                        using var imageSource = imageSourceFactory.CreateFromStream(imageStream);
                        var art = asciiArtGenerator.GenerateAsciiArtFromImage(
                            imageSource,
                            AsciiGenerationOptions.Default with
                            {
                                ScaleDownFactor = request.ScaleDownFactor,
                                HorizontalStretchFactor = request.HorizontalStretchFactor
                            });

                        AsciiArtResponse response = new(
                            art.Art,
                            art.Width,
                            art.Height);
                        return Results.Ok(response);
                    });

                return new RouteRegistrationDependencyMarker();
            })
            .SingleInstance();
    }
}

The code above gives us a POST route on the server that's essentially just <server>/ascii/url, which takes a URL that my server can work with. I'd like to opt for switching over to an API where image bytes can be uploaded, but this seemed like an easy way to get started.

This isn't going to be a full tutorial on deploying to Azure by any means but using "App Service" within Azure, I have full git-actions integration so that every time I make a commit, my application is deployed.

My Zuplo API is Alive!

Now that my own server is deployed in Azure, it's time to rock and roll! This is where the simplicity of Zuplo really shines. Here's the new route I added with URL rewriting:

Zuplo ASCII Art Route Forwarding

I literally only had to press the save button (not shown in the screenshot) and within about 3 seconds the test button became live for me to use. Directly from the browser, much like using Postman, I was able to send a test URL and get back some fancy ASCII art. I'll skip the screenshot because Zuplo isn't optimized to try rendering ASCII art for us -- I know, I know... a critical flaw!


Security Between Azure and Zuplo API Gateway

As I was putting all of this together, I said "Wait a sec! My server is still exposed to the Internet without ANY protection in front of it!". This is realistically not a *huge* problem at the moment because nobody even knows the address of it. But still -- I wanted to do this properly.

I reached out to Zuplo and asked "Hey, what's your recommended way to do this?", just in case they had more magic up their sleeves. They politely explained and mentioned that's also covered in the example getting started documentation. And obviously, I read that ahead of time... we ALWAYS read the docs... right?

RIGHT?

After reading the documentation, I had one little change to make on my server, which was using a custom middleware for adding API authentication. By the way, if I haven't sold you on plugin architecture before, API key authentication for my server was just another plugin:

internal sealed class ApiKeyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        builder
            .Register(ctx =>
            {
                var app = ctx.Resolve<WebApplication>();
                var config = ctx.Resolve<IConfiguration>();

                const string ApiKeyConfigKey = "<APP_CONFIG_KEY_HERE>";
                Lazy<string?> lazyApiKey = new(() => config.GetValue<string>(ApiKeyConfigKey));

                app.Use(async (context, next) =>
                {
                    const string ApiKeyHeader = "<HEADER_KEY_GOES_HERE>";
                    if (!context.Request.Headers.TryGetValue(ApiKeyHeader, out var apiKeyVal))
                    {
                        context.Response.StatusCode = 401;
                        await context.Response.WriteAsync("Api Key not found.");
                    }

                    var apiKey = lazyApiKey.Value;
                    if (!string.Equals(apiKey, apiKeyVal, StringComparison.Ordinal))
                    {
                        context.Response.StatusCode = 401;
                        await context.Response.WriteAsync("Invalid API Key.");
                    }

                    await next(context);
                });

                return new PostAppRegistrationDependencyMarker();
            })
            .SingleInstance();
    }
}

Now one more super quick change to Zuplo to add a policy to add my configured API key that I have on my server, and we're done:

Zuplo Policy Set Headers

What's Next for API Gateways in Zuplo?

At this point, I have a public API through Zuplo that can get hit, uses an API key with my own server, forwards that request, and gets the response. It's a full end-to-end integration which I *would* have loved to do right within Zuplo -- but my Typescript skills were mocking me. That will certainly be an exercise for the future because I think it would be awesome to have this one truly at the edge node.

But before I can make this API public, I still need a few more things... And I never actually accomplished them yet:

  • API Documentation
  • Rate limiting
  • Authentication
  • Monetization!

The good news is that since I have my API integrated with Zuplo now, a lot of what's left is almost all drag-and-drop in their portal! I don't suspect I'll have to code anything inside of my own server at this point.


Wrapping Up API Gateways in Zuplo

Overall, I thought this was an awesome platform to work with. Getting API Gateways in Zuplo setup is simple and quick -- it's hard to argue with those two things. But if you consider people like me who don't want to go hand-roll this kind of stuff per service they create, the fact that they've integrated so many building blocks makes this dreamy. There were policies in the catalog that made me think "Oh crap! I didn't even consider that!".

I'm looking forward to continuing to build this API out in Zuplo and getting it to a point where I can monetize it! So keep your eyes peeled -- because you'll soon have an ASCII art API you can integrate with. While yes, this post is sponsored by Zuplo , I can say without a doubt their platform greatly simplifies a lot of the pain points I never enjoy dealing with when I develop.

Creating API Gateways in Zuplo - Dev Leader Weekly 40

Welcome to another issue of Dev Leader Weekly! In this issue sponsored by Zuplo, I share how I'm putting an API gateway in front of my ASCII art API!

How to Build An ASP.NET Core Web API: A Practical Beginner's Tutorial

Learn how to build an ASP.NET core web API! This tutorial for beginners will guide you through setting up the project to building the API endpoints.

Generate ASCII Art - A Simple How To in C#

Have you ever wanted to change a picture into ASCII art? Now you can with your very own C# program that can generate ASCII art! Sample code included!

An error has occurred. This application may no longer respond until reloaded. Reload x