# Create posts

By following this beginner tutorial, you will end up with a simple blog app that is powered by the Cosmos SDK.

# Requirements

For this tutorial we will be using Starport (opens new window) v0.13.2, an easy to use tool for building blockchains. To install starport into /usr/local/bin, run the following command:

Copy curl https://get.starport.network/starport@v0.13.2! | bash

You can also use Starport v0.13.2 on the web in a browser-based IDE (opens new window). Learn more about other ways to install Starport (opens new window).

# Getting Started

Let's get started! The first step is to install the starport (opens new window) CLI tool.

After starport is installed, use it to create the initial app structure inside a directory named blog:

Copy starport app github.com/example/blog

One of the main features of Starport is code generation. The command above has generated a directory structure with a working blockchain application. Starport can also add data types to your app with starport type command. To see it in action, follow the poll application tutorial. In this guide, however, we'll create those files manually to understand how it all works under the hood.

# Overview

Let's take a quick look at what Starport has generated for us. app/app.go (opens new window) file imports and configures SDK modules and creates a constructor for our application that extends a basic SDK application (opens new window) among other things. This app will use only a couple standard modules bundled with Cosmos SDK (including auth for dealing with accounts and bank for handling coin transfers) and one module (x/blog) that will contain custom functionality.

In cmd directory we have source files of two programs for interacting with our application: blogd starts a full-node for your blockchain and enables you to query the full-node, either to update the state by sending a transaction or to read it via a query.

This blog app will store data in a persistent key-value store (opens new window). Similarly to most key-value stores, you can retrieve, delete, update, and loop through keys to obtain the values you are interested in.

We’ll be creating a simple blog-like application, so let’s define the first proto type, the Post in the post.proto file.

# proto/blog/post.proto

Copy syntax = "proto3"; package example.blog.blog; option go_package = "github.com/example/blog/x/blog/types"; import "gogoproto/gogo.proto"; message Post { string creator = 1; string id = 2; string title = 3; string body = 4; } message MsgCreatePost { string creator = 1; string title = 2; string body = 3; }

The code above defines the three properties of a post: Creator, Title, Body and ID. We generate unique global IDs for each post and also store them as strings.

Posts in our key-value store will look like this:

Copy "post-0": { "Creator": "cosmos18cd5t4msvp2lpuvh99rwglrmjrrw9qx5h3f3gz", "Title": "This is a post!", "Body": "Welcome to my blog app.", "ID": "0" }, "post-1": { ... }

Right now the store is empty. Let's figure out how to add posts.

With the Cosmos SDK, users can interact with your app with either a CLI (blogd) or by sending HTTP requests. Let's define the CLI command first. Users should be able to type blogd tx blog create-post 'This is a post!' 'Welcome to my blog app.' --from=user1 to add a post to your store. The create-post subcommand hasn’t been defined yet--let’s do it now.

# x/blog/client/cli/tx.go

In the import block, make sure to import these four packages:

Copy import ( "fmt" "github.com/spf13/cobra" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/client/tx" // "github.com/cosmos/cosmos-sdk/client/flags" "github.com/example/blog/x/blog/types" )

This file already contains func GetTxCmd which defines custom blogd commands (opens new window). We will add the custom create-post command to our blogd by first adding GetCmdCreatePost to blogTxCmd.

Copy cmd.AddCommand(CmdCreatePost())

At the end of the file, let's define GetCmdCreatePost itself.

Copy func CmdCreatePost() *cobra.Command { cmd := &cobra.Command{ Use: "create-post [title] [body]", Short: "Creates a new post", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { argsTitle := string(args[0]) argsBody := string(args[1]) clientCtx, err := client.GetClientTxContext(cmd) if err != nil { return err } msg := types.NewMsgCreatePost(clientCtx.GetFromAddress().String(), string(argsTitle), string(argsBody)) if err := msg.ValidateBasic(); err != nil { return err } return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) }, } flags.AddTxFlagsToCmd(cmd) return cmd }

The function above defines what happens when you run the create-post subcommand. create-post takes two arguments [title] [body], creates a message NewMsgCreatePost (with title as args[0] and args[1]) and broadcasts this message to be processed in your application.

This is a common pattern in the SDK: users make changes to the store by broadcasting messages (opens new window). Both CLI commands and HTTP requests create messages that can be broadcasted in order for state transition to occur.

# x/blog/types/messages_post.go

Let’s define NewMsgCreatePost in a new file you should create as x/blog/types/messages_post.go.

Copy package types import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) var _ sdk.Msg = &MsgCreatePost{}

Similarly to the post proto, MsgCreatePost contains our post definition.

Copy func NewMsgCreatePost(creator string, title string, body string) *MsgCreatePost { return &MsgCreatePost{ Creator: creator, Title: title, Body: body, } }

NewMsgCreatePost is a constructor function that creates the MsgCreatePost message. The following five functions have to be defined to implement the Msg interface. They allow you to perform validation that doesn’t require access to the store (like checking for empty values), etc.

Copy // Route ... func (msg MsgCreatePost) Route() string { return RouterKey } // Type ... func (msg MsgCreatePost) Type() string { return "CreatePost" } // GetSigners ... func (msg *MsgCreatePost) GetSigners() []sdk.AccAddress { creator, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { panic(err) } return []sdk.AccAddress{creator} } // GetSignBytes ... func (msg *MsgCreatePost) GetSignBytes() []byte { bz := ModuleCdc.MustMarshalJSON(msg) return sdk.MustSortJSON(bz) } // ValidateBasic ... func (msg *MsgCreatePost) ValidateBasic() error { _, err := sdk.AccAddressFromBech32(msg.Creator) if err != nil { return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } return nil }

Going back to GetCmdCreatePost in x/blog/client/cli/tx.go, you'll see MsgCreatePost being created and broadcast with GenerateOrBroadcastMsgs.

After being broadcast, the messages are processed by an important part of the application, called handlers (opens new window).

# x/blog/handler.go

You should already have func NewHandler defined which lists all available handlers. Modify it to include a new function called handleMsgCreatePost.

Copy switch msg := msg.(type) { case *types.MsgCreatePost: return handleMsgCreatePost(ctx, k, msg) default:

Let's create the handler in handler_post.go file

# x/blog/handler_post.go

Now let’s define handleMsgCreatePost in a new file handler_post.go:

Copy package blog import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/example/blog/x/blog/keeper" "github.com/example/blog/x/blog/types" ) func handleMsgCreatePost(ctx sdk.Context, k keeper.Keeper, msg *types.MsgCreatePost) (*sdk.Result, error) { k.CreatePost(ctx, *msg) return &sdk.Result{Events: ctx.EventManager().ABCIEvents()}, nil }

After creating a post object with creator, ID and title, the message handler calls k.CreatePost(ctx, post). “k” stands for Keeper (opens new window), an abstraction used by the SDK that writes data to the store. Let’s define the CreatePost keeper function in a new keeper/post.go file.

# x/blog/keeper/post.go

First, create a new file post.go in the keeper/ directory. Then, add a CreatePost function that takes two arguments: a context (opens new window) and a post. Also, GetPostCount and SetPostCount functions.

Copy package keeper import ( "strconv" "github.com/cosmos/cosmos-sdk/store/prefix" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/example/blog/x/blog/types" ) // GetPostCount get the total number of post func (k Keeper) GetPostCount(ctx sdk.Context) int64 { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostCountKey)) byteKey := types.KeyPrefix(types.PostCountKey) bz := store.Get(byteKey) // Count doesn't exist: no element if bz == nil { return 0 } // Parse bytes count, err := strconv.ParseInt(string(bz), 10, 64) if err != nil { // Panic because the count should be always formattable to int64 panic("cannot decode count") } return count } // SetPostCount set the total number of post func (k Keeper) SetPostCount(ctx sdk.Context, count int64) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostCountKey)) byteKey := types.KeyPrefix(types.PostCountKey) bz := []byte(strconv.FormatInt(count, 10)) store.Set(byteKey, bz) } func (k Keeper) CreatePost(ctx sdk.Context, msg types.MsgCreatePost) { // Create the post count := k.GetPostCount(ctx) var post = types.Post{ Creator: msg.Creator, Id: strconv.FormatInt(count, 10), Title: msg.Title, Body: msg.Body, } store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey)) key := types.KeyPrefix(types.PostKey + post.Id) value := k.cdc.MustMarshalBinaryBare(&post) store.Set(key, value) // Update post count k.SetPostCount(ctx, count+1) } func (k Keeper) GetPost(ctx sdk.Context, key string) types.Post { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey)) var post types.Post k.cdc.MustUnmarshalBinaryBare(store.Get(types.KeyPrefix(types.PostKey + key)), &post) return post } func (k Keeper) HasPost(ctx sdk.Context, id string) bool { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey)) return store.Has(types.KeyPrefix(types.PostKey + id)) } func (k Keeper) GetPostOwner(ctx sdk.Context, key string) string { return k.GetPost(ctx, key).Creator } func (k Keeper) GetAllPost(ctx sdk.Context) (msgs []types.Post) { store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.PostKey)) iterator := sdk.KVStorePrefixIterator(store, types.KeyPrefix(types.PostKey)) defer iterator.Close() for ; iterator.Valid(); iterator.Next() { var msg types.Post k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &msg) msgs = append(msgs, msg) } return }

CreatePost creates a key by concatenating a post prefix with an ID. If you look back at how our store looks, you’ll notice keys have prefixes, which is why post-0bae9f7d-20f8-4b51-9d5c-af9103177d66 contained the prefix post- . The reason for this is you have one store, but you might want to keep different types of objects in it, like posts and users. Prefixing keys with post- and user- allows you to share one storage space between different types of objects.

# x/blog/types/keys.go

To define the post prefix add the following code:

Copy package types const ( // Other constants... // PostPrefix is used for keys in the KV store PostKey= "Post-value-" PostCountKey= "Post-count-" )

# x/blog/types/codec.go

Finally, store.Set(key, value) writes our post to the store. Two last things to do is tell our encoder (opens new window) how our MsgCreatePost is converted to bytes.

Copy package types import ( "github.com/cosmos/cosmos-sdk/codec" cdctypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" ) func RegisterCodec(cdc *codec.LegacyAmino) { // this line is used by starport scaffolding # 2 cdc.RegisterConcrete(&MsgCreatePost{}, "blog/CreatePost", nil) } func RegisterInterfaces(registry cdctypes.InterfaceRegistry) { // this line is used by starport scaffolding # 3 registry.RegisterImplementations((*sdk.Msg)(nil), &MsgCreatePost{}, ) } var ( amino = codec.NewLegacyAmino() ModuleCdc = codec.NewAminoCodec(amino) )

# Launch

Now we are ready to build and start our app and create some posts.

To launch your application run:

Copy starport serve

This command installs dependencies, builds and initializes the app, and runs servers. You can also do it manually:

Fist, create a Makefile in your /blog root directory

# Makefile

Copy PACKAGES=$(shell go list ./... | grep -v '/simulation') VERSION := $(shell echo $(shell git describe --tags) | sed 's/^v//') COMMIT := $(shell git log -1 --format='%H') ldflags = -X github.com/cosmos/cosmos-sdk/version.Name=blog \ -X github.com/cosmos/cosmos-sdk/version.ServerName=blogd \ -X github.com/cosmos/cosmos-sdk/version.Version=$(VERSION) \ -X github.com/cosmos/cosmos-sdk/version.Commit=$(COMMIT) BUILD_FLAGS := -ldflags '$(ldflags)' all: install install: go.sum @echo "--> Installing blogd" @go install -mod=readonly $(BUILD_FLAGS) ./cmd/blogd go.sum: go.mod @echo "--> Ensure dependencies have not been modified" GO111MODULE=on go mod verify test: @go test -mod=readonly $(PACKAGES)
  1. go mod tidy cleans up dependencies.
  2. make builds your app and creates a binary in your go path: blogd.
  3. Initialization scripts in the Makefile removes data directories, configures your app and generates two accounts. By default your app stores data in your home directory in ~/.blogd. The script removes them, so every time you have a clean state.
  4. blogd start launches your app. After a couple of seconds you will see hashes of blocks being generated. Leave this terminal window open and open a new one.

Note: depending on your OS and firewall settings, you may have to accept a prompt asking if your application's binary (blogd in this case) can accept external connections.

Run the following command to create a post:

Copy blogd tx blog create-post "My first post" "This is a post\!" --from=user1

“This is a post!” is a title for our post and --from=user1 tells the program who is creating this post. user1 is a label for your pair of keys used to sign the transaction, created by the initialization script located within the /Makefile previously. Keys are stored in ~/.blogd.

After running the command and confirming it, you will see an object with “txhash” property with a value like CA1491B39384A4F29E568F62B156E0F2D0601507EF499CE1B8F3930BAFE7F03C.

To verify that the transaction has been processed, open a browser and visit the following URL (make sure to replace CA14... with the value of your txhash but make sure to have the 0x prefix):

Copy http://localhost:26657/tx?hash=0xCA1491B39384A4F29E568F62B156E0F2D0601507EF499CE1B8F3930BAFE7F03C

Also check out a basic block overview at

Copy http://localhost:12345/#/blocks

Congratulations! You have just created and launched your custom blockchain and sent the first transaction 🎉

# Errors

# Unknown command "create-post" for "blog"

Copy blogd tx blog create-post 'Hello!' 'My first post' --from=user1 ERROR: unknown command "create-post" for "blog"

Make sure you’ve added cmd.AddCommand(CmdCreatePost()), to func GetTxCmd in x/blog/client/cli/tx.go.

# Unrecognized blog message type

Copy blogd tx blog create-post 'Hello!' 'My first post' --from=user1 ERROR: unrecognized blog message type

Make sure you have added case *types.MsgCreatePost: return handleMsgCreatePost(ctx, k, msg) to func NewHandler in x/blog/handler.go

# Cannot encode unregistered concrete type

Copy blogd tx blog create-post Hello! --from=user1 panic: Cannot encode unregistered concrete type types.MsgCreatePost.

Make sure you’ve added cdc.RegisterConcrete(MsgCreatePost{}, "blog/CreatePost", nil) to func RegisterCodec in x/blog/types/codec.go.

# not found: key not found

Copy Error: rpc error: code = NotFound desc = account cosmos1t3rafxvy3ggluchm5sjzetj9wt50eq9hjay6f2 not found: key not found

Make sure that you wait for the first block to be created after bootstrapping a chain again.