Skip to content

Latest commit

 

History

History

22.Encore

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 

Workshop 22 - Encore 🚀

✔️ Discover Encore a powerful go framework to develop APIs.

✔️ Learn services logics

✔️ Improve your Go and architecture design skills

Introduction

With the evolution of the web the arrival of SaaS, PaaS, IaaS, developing and deploying an API has become a must-have knowledge. More and more frameworks exist to code faster, optimize deployment, remove boring boilerplate but there is no solution that manage both of those things for you.

Encore does, it's a powerful golang framework that help you to build production-ready API and services without coding boilerplate, pay attention to the deployment or manage your database. In conclusion, Encore helps you to stay focus on the most important thing: your product.

In this workshop, we will create a simple delivery application that will manage packets to understand and uses all features available by Encore.

Step 0 - Setup

Before starting the workshop, make sure you've completed all steps of the setup.

Step 1 - Discover Encore

We are not going to develop an API with a real use-case now, to begin we will write some endpoints to understand how Encore handle requests and responses.

First, create a new folder named discovery and create the file discovery.go in it.

With encore, each directory that contains an endpoint is considered as a services.

We are going to create 3 endpoints:

1 - URLParam

That endpoint aim to retrieve the parameter from the url.

Create the function URLParam which takes a string named param as url parameter. It should return an object of type URLParamResponse which contains a field Message of type string.

⚠️ The message's content must be Received a query with '<$param>' as url parameter.

💡 You must configure your function to be reached through a request to /url/:param with the GET method.

Example

curl http://localhost:4000/url/test
{
  "message": "Received a query with 'test' as url parameter"
}

2 - QueryParam

This one aim to retrieve the parameter from query parameters

Create the function QueryParam which takes as query parameter two fields:

  • Foo of type string
  • Bar of type int

It should return an object of type QueryParamResponse which contains a field Result of type string.

⚠️ The result's content must be Received a query with param foo='<$Foo>' and param Bar='<$Bar>'

💡 You must configure your function to be reached through a request to /query with the GET method

Example

curl http://localhost:4000/query\?foo\=test\&bar\=3
{
  "result": "Received a query with param Foo='test' and param Bar='3'"
}

3 - BodyParam

The last one aim to retrieve values from the body.

Create the function BodyData which takes a body with two fields:

  • Title of type string
  • Content of type string

It should return an object of type BodyDataResponse which contains a field Message of type string

⚠️ The message's content must be Received a body with title ='<$Title>'' and content='<$Content>'

💡 You must configure your function to be reached through a request to /body with a POST method.

Use the dashboard to test your function, it should be easier than a curl command.

Step 2 - Delivery's API

Now it's time to start the real subject.

The purpose is to create a simple delivery API that will manage packets & deliverymen.

Here's a simple architecture to illustrate the proposal

delivery api architecture

We will store our data in an SQL database managed by encore.

Then we will create a client and handle database initialization in database service. The client will be forward to our other services:

  • packet: manage packets
  • delivery: manage delivery
  • deliveryman: manage deliverymen

This way, we got independent services that share the same database to simplify data management, especially when we will need to handle relation between our tables.

deliverymen will be protected with an authentication process explained further.

Database

First, create a new folder named delivery, this is where we will code our delivery-api.

Before coding our API, we must define models and create our database.
We are lucky, encore manage the boring stuff for you, you only have to create a migrations directory in a service that will store your sql files and everything will work automatically.

Let's create our database:

  • Create a database service
  • Create a directory migrations in your service
  • Create a file named 1_create_database.up.sql in that folder

Paste the following table definition

--  
-- Extensions  
--  
CREATE  
EXTENSION IF NOT EXISTS "uuid-ossp";

--  
-- Packets  
--  
CREATE TYPE PacketStatus as ENUM('Send', 'Traveling', 'Received');  
  
DROP TABLE IF EXISTS packet;  
CREATE TABLE packet  
(  
 id             uuid         DEFAULT uuid_generate_v4() PRIMARY KEY,  
 owner          VARCHAR,  
 receiver       VARCHAR,  
 destination    VARCHAR,  
 status         PacketStatus DEFAULT 'Send',  
);

The purpose of this workshop is not to waste your time with postgresql, we keep it for another workshop 😄

Then, you will need to define your entities as go code to easily manage it in our API

  • Create a folder entities in database
  • Create a file entities.go.
  • In this file, you will define a struct Packet with fields corresponding to the database model.

💡 Don't lost time defining an enumeration if you are not familiar with Go.

You will also need to export your database client to execute SQL query on your models.

Copy the content below in a file named client.go

package database  
  
import (  
 "context"  
  
 "encore.dev/storage/sqldb"  
 "github.com/jmoiron/sqlx"
 _ "github.com/lib/pq"  
)  
  
var (  
 // Package name is the database name  
 rawDb = sqldb.Named("database").Stdlib()  
 db    = sqlx.NewDb(rawDb, "postgresql")  
)  
  
// Client give access to the database client
func Client() *sqlx.DB {  
 return db  
}  
  
// Placeholder used to create the SQL database
//encore:api private  
func Placeholder(_ context.Context) error {  
 return nil  
}

Here we are exporting a client from sqlx ORM, a wrapper around sql to execute query easily.

💡 Note that encore provide his own version of the sql package named sqldb.

💡 In this workshop, you will need to write raw SQL queries to interact with the database. To help you, look at that little cheatsheet with all queries involved.

Packet's time

Now we will create our endpoints to manage our packets.

Create a new service named packet in the delivery folder.

First, we must retrieve our database client, to do so a file named packet.go that will export a variable db initialize by database.Client().

The init function could be useful.

Next, let's expose endpoints to read our packets.

Create the file get.go that will contain two functions:

  • GetAll that will retrieve all packets stored in the database.
    You must return an object with the list of all packets and the number of package.
    Your endpoint must be public and reachable with a request GET on /packet

  • Get that will retrieve one packet selected by his unique identifier.
    Your endpoint must be public and reachable with a request GET on /packet/:id where :id is your identifier

Do you remember how works url parameters ?

Great, we read data, but isn't better if we can add some to test our endpoints ?

Let's create the file add.go that will handle the creation of packet.

Create the function Add that will take as parameter a CreateDTO which contains an owner, a receiver and a destination.
You will create a new packet in your database from this information and return a message of confirmation if everything went well.
Your endpoint must be public and reachable with a request POST on /packet

Perfect, now verify your endpoint with the awesome encore dashboard created when you run encore run.

Bonus

If you are a brave developer, you can continue with Update and Delete. You got the logic so it shouldn't be a big deal for you now 🚀

Step 3 - Hire employee

Fun begin, we can receive and manage packet but no one can send it to their destination.

How about creating a new service to handle deliveryman ?

First, we must improve your database service to handle the relation between packets & deliveryman. Since encore doesn't currently support powerful ORM like ent or goORM, we will need to query our data with raw SQL.
It's not really easy for a beginner so to keep concentrate on encore, you will replace your database service with the one provide in the workshop.

Your database service should now have the following architecture.

├── client.go
├── entities
│   ├── entities.go
│   └── status.go
├── migrations
│   ├── 1_create_database.up.sql
│   └── 2_create_views.up.sql
├── result.go
└── utils
    └── buildUpdateArgs.go

Now, create a new deliveryman service at the root of your folder delivery and a file that will retrieve your database client.

Our first employee

Let's take a look at the deliveryman table in the database

CREATE TYPE DeliveryManStatus as ENUM('Available', 'Working');  
  
DROP TABLE IF EXISTS deliveryman;  
CREATE TABLE deliveryman  
(  
 id     uuid           DEFAULT uuid_generate_v4() PRIMARY KEY,  
 name   VARCHAR,  
 status DeliveryManStatus DEFAULT 'Available'  
);

As you see, the only required property is name.

So now, create the file add.go in that new service.

In this one, write a function Add that will take a CreateDTO as parameter that contains the deliveryman's name.
Then, add it to your database and return a message of confirmation.
Your endpoint must be reachable with a request POST on path /deliveryman

What's the passphrase sir ?

If you don't notice, there was no instruction about the kind of endpoint your Add function must be, is it public, private or auth ?
We don't really want to allow anyone adding an employee so we should be able to authenticate the one that trying to create a resource on our service.
Storing the caller identity will be too long for a workshop and involve to design login and logout endpoint, instead we will just use a simple passphrase as proof of our identity.

⚠️ I DON'T recommend that method in production API, it's just to win time.

Encore framework provide an elegant way to manage our authentication system through a simple keyword: authHandler.

Create a new service named auth in the delivery folder.

Use the secrets integration of encore to create a secret variable named Token that will store your passphrase.
Then create a function named AuthHandler that will check that the Authorization header contains the same value of our token. In case of error, return a custom error with error code 401 and a an error message.

Don't forget the annotation. You can use any word as passphrase.

Now you can add the keyword auth to the function Add.

Great, your endpoint is protected ! Test it with the dashboard.

Manage deliverymen

You are now ready to complete other function.

We want to:

  • List all deliverymen
  • Get a deliveryman information selected by his ID

All those endpoints must be only reachable by an authenticated person so don't forget the auth annotation.

As a bonus, you can also write update & delete endpoints.

Step 4 - Send request

We have deliverymen and packets, the only missing part is the delivery service.

Create it in the delivery folder (yes they are nested).

Send & receive

As usual, retrieve your database client in a file named delivery.go.

Now let's expose your functions.

  • Create a function SendPacket that will take as argument SendPacketDTO which contains the packetID and the deliverymanID.
    You function must update the target packet status and assign that one to the deliveryman. You also need to update your deliveryman's status. Return a message of confirmation if everything went well.

  • Create a function ReceivePacket that will do the same as SendPacket but with different status. Your deliveryman should be available after the customer received his packet.

Those two functions must be public and reachable with a request POST. You are free to choose the name of your endpoints.

💡 You can also write some error handling about status. For example, a deliveryman with the status working can not handle an other delivery request.

Who's the owner

💡 This step is a bonus.

I think you notice it but we can't actually see the links between deliverymen and packets when we retrieve our list of packet or deliverymen.

That would be awesome to get something like this when we reach /deliveryman

{
  "number": 3,
  "deliverymans": [
    {
      "id": "5a79fe3c-fd69-4e09-9332-cd9179a5fe41",
      "name": "John",
      "status": "Available",
      "packets": [
        {
          "id": "c52657bd-1bb0-4add-a0de-d5df24022232",
          "owner": "Botea",
          "receiver": "Mr Who",
          "destination": "Moon",
          "status": "Send"
        },
        {
          "id": "0cd01b01-c850-42d2-a451-72de4469fa45",
          "owner": "MoDonald",
          "receiver": "MrKing",
          "destination": "Hell",
          "status": "Traveling"
        }
      ]
    },
    {
      "id": "19a7b91f-8b57-4512-be66-d53b2602178b",
      "name": "Sonic",
      "status": "Available",
      "packets": [
        {
          "id": "65428358-fb2e-4fbb-a6a3-82f114f2b86e",
          "owner": "Knukle",
          "receiver": "Peach",
          "destination": "Smash",
          "status": "Received"
        }
      ]
    },
    {
      "id": "ae05ea27-9a24-4fdf-87dd-70260a4ad40d",
      "name": "Mr Jones",
      "status": "Working"
    }
  ]
}

Find a way to get that result.

💡 They are views created when initialized the database, maybe that could help you. You can find the sql file in database/migrations/.

Congrats! You finished that workshop and build your own delivery API with Encore framework.

Bonus

To go further

Authors


Tom Chauveau

Organization


LinkedIn logo Instagram logo Twitter logo Discord logo

Website logo

🚀 Don't hesitate to follow us on our different networks, and put a star 🌟 on PoC's repositories.