Making A Lightning Web App: Part 2

Setting up a frontend and taking our very first payment

William O'Beirne
May 30 · 7 min read

Hey, this is part 2 of a 5 part series on building a Lightning app. If you haven’t read part 1, you might want to check it out.

We left off from the previous tutorial with a basic web server that had a single page for serving up our node’s information. While it was a good start to understand how we communicate with our node, we need a proper frontend with interactivity and style, as well as expand the server to store and provide user data to really consider our project an ‘app’.

The idea for this tutorial application is to make a Twitter-style post feed, that users need to pay 1 satoshi per character to make posts. While not the most innovative or ground breaking application, hopefully this will be a good starting point to inspire you to come up with your own interesting use-cases that integrate micro-payments.

For this portion of the tutorial, we’re adding Webpack, React, and Bootstrap to the mix. Webpack will compile our code into files the browser knows how to deal with. React is the framework that will manage our frontend logic and rendering. Bootstrap will provide us with some nice components and styles so that we won’t have to write that all from scratch.

Let’s dive in!


Getting Started: Clone / Fetch the (new) Project

Like last time, we’ll start by cloning a further iteration of the project. This comes from the same repository, but it’s on a different branch. You can either clone the repository fresh, or if you already had it from last time, just fetch the latest data and checkout the branch:

# Run this if you don't already have the repository
git clone https://github.com/wbobeirne/lightning-app-tutorial.git
cd lightning-app-tutorial
git checkout part-2

# Run this inside the repository directory if you already had it
git fetch origin
git checkout part-2

Like before, you’ll need to install the dependencies we need. We’ve added a few new ones, so you’ll need to re-run this in the project directory:

npm install

Environment Configuration

In addition to new dependencies, there are also some changes and additions to our old environment variables in the .env file:

# Server configuration
PORT=3001
# Client configuration
API_PATH="http://localhost:3001/api"
# LND Node configuration
LND_MACAROON="abc123..."

We’ve got to make the following changes:

  • Bump the PORT variable for Express up to 3001 — Our client application will be served up by webpack-dev-server in development. This keeps our React client code and our REST API separate for now.
  • Add the API_PATH configuration — Since our REST API is served up on a different server than our frontend code, we need to tell the frontend where to go to make API requests.
  • Update the LND_MACAROON to use invoice.macaroon — Before we were using the readonly.macaroon , but now we’ll need to generate invoices, which means we need more permissions. You’ll need to do the same base64 encoding we did in the previous post on invoice.macaroon .

Once you’ve made those changes, you should be all ready to go.


Project Overview

We now have a new folder in our project called client/. This will contain the code and configuration for the frontend. There's a lot of new code here, but the majority of it is standard React / Webpack boilerplate you see in a lot of projects. I won’t be covering most of that as there are other tutorials that will do a much better job than I ever could, so feel free to take a look elsewhere before continuing on.

There are also some changes to the Express server to make it function as a REST API for our frontend application to make requests against, and to maintain the posts that users submit.

The code we’ll be going over here will strictly be the stuff that interacts with our Lightning Node, and our server’s API. Those files will be:

  • server/index.ts — New API-style routes have been added that our client will hit to fetch and create user-submitted posts.
  • client/lib/api.ts — A simple API interface that handles requests to our API for those new routes.
  • client/components/PostForm.tsx — A form component for making a new post on our site, and paying the associated invoice.
  • client/components/Posts.tsx — A list component that displays all of the posts that have been made & paid for.

Now that we’ve got that overview, onto the code!


server/index.ts

Returning to our simple server from the previous tutorial, we now have a few new routes that all interact with a Posts model provided by the server/posts.ts file. I won't be going over what's in there, since it's just a basic Javascript class with methods that add and remove post data to a list stored in memory (which means your posts will disappear whenever you restart the server, so don’t be alarmed!)

Our first two routes are quite simple. The GET /api/posts endpoint returns an array of posts that have been paid for, ordered by most recent. The GET /api/posts/:id endpoint is one that gets a specific post, regardless of its payment status. These will be used for listing public posts, and checking on the status of your own post respectively.

The next, and more complicated route here is the POST /api/posts endpoint, which will submit a new post, and generate an invoice associated with it that needs paying for:

This creates a new Post object, which is initially marked as unpaid. We then generate an invoice with our node, setting the value (cost in satoshis) to be the number of characters in content. We also embed the post ID number in the memo. This will allow us to look up the post later once it’s been paid. We then return the unpaid post object, as well as the BOLT-11 payment request string to the user for them to pay.

It’s important to manage this data on the server, rather than the client. You don’t want to trust the client with logic around payments, as a savvy, nefarious user might send your server bad data, such as an extremely long post that they only pay 1 satoshi for, or find a way to bypass payment altogether.

Finally, down in our initialization logic, we have a bit of code that watches an invoice stream for paid invoices:

Here we get a Readable stream object from our node API that allows us to tap into every invoice update from our node. This stream will cause the .on('data') callback to trigger when invoices are created or paid, so we have a little logic in it to exclude:

  • Newly created invoices that haven’t been paid yet
  • Invoices that are potentially unrelated to our app and don’t have a memo that’s formatted with a post ID.

After we’ve ignored those, we can mark the post as paid with the ID we pulled out of the memo. The next time a user requests the list of paid posts via GET /api/posts, this one will now show up.

Now, onto the new client code that will be running in the user’s browser:

client/lib/api.ts

This provides us nicely typed functions for our server API in a way that gives us methods that return promises for the endpoints we just went over. Don’t worry too much about the code in here, just know that we can call these methods now to get data from our server, or handle any errors that may happen. We’ll see how the API gets used in the next two files:

client/components/Posts.tsx

Here we’re fetching and displaying any and all posts that users have made (and paid for!) We make a request to our API to fetch the posts. If the request succeeds or fails, we store the posts or error message respectively in component state. The render method (not shown here) then renders markup based on what’s in state.

components/PostForm.tsx

The PostForm component has two important methods that we'll take a look at one at a time:

After filling out and submitting our form, the post is submitted to the backend. and if all goes well we get back the res object with post data, as well as a BOLT-11 paymentRequest string. We'll set these to our component's state so that we can render the payment request and a button to open with a wallet on our computer.

We also call the checkIfPaid method when the submission succeeds:

This method will start checking if we’ve paid for the post that we’re currently showing an invoice for. It waits a second, checks, and if it hasn’t been paid, calls itself again recursively. This will run forever until we pay the invoice, which will cause it to refresh the page. Or if we leave the page, the post will never be made public, and the invoice will eventually expire.


The Result

With that all taken care of, all that’s left is to run the thing with npm run dev and check out our functioning Lightning app!

There’s a bit of glue connecting all of this logic together that I didn’t go over explicitly, so feel free to poke around on your own to see how everything works. Or reach out to me with any specific questions.

And by all means, tinker with the thing. Make the cost increase exponential, or replace text posts with images, links, or tweets. Maybe add some of your own styling to really make it yours. Go nuts!


There’s still so much to improve here. Up next in part 3, we’ll replace the janky behavior of long-polling and refreshing the page with instantly-updating websockets. Get started on it out now!