Skip to main content

Create a Web App on Flow


This guide is a simple walkthrough to get started building a web3 app using the Flow Client Library (FCL).

info

If you are looking for a scaffolds, check out scaffolds available in the Flow CLI.

This simple guide uses the "Hello World" scaffold, it is meant to provide all the code needed to get a web application up and running.

Introduction

Flow app anatomy

info

FCL (aka Flow Client Library) wraps the logic needed to communicate with the Flow blockchain. It's a npm package, More Information

This guide assumes a good understanding of React. The concepts are easy to understand and apply to other libraries and framework. A strong understanding of Cadence (Flow's smart contract language) is not required. More information on Cadence, learning the Cadence language.

FCL concepts covered:

For more help, Discord. See links at the end of this article for diving deeper into building on Flow.

Installation

Make sure you have Flow CLI installed. installation instructions.

Configuration

Setting up Flow

Using the Flow CLI scaffold, create a basic hello world web project with Web Scaffolds -> [5] FCL Web Dapp. This will create a new flow.json file in the hello-world folder. This file will contain the Flow configuration for your project.


_10
flow init hello-world --scaffold
_10
# select scaffold 5
_10
cd hello-world
_10
npm install
_10
# run the app in a later step

We don't recommend keeping private keys in your flow.json, notice that Flow CLI already moved the emulator private key to a emulator.key file and point to it using the key/location pattern. This file should be added to your .gitignore file, so it won't be committed to your repository.

We won't be using emulator and running contracts locally in this quickstart, but FCL will complain if it finds private keys in your flow.json file.

Your flow.json file should look like this:


_16
{
_16
"networks": {
_16
"emulator": "127.0.0.1:3569",
_16
"mainnet": "access.mainnet.nodes.onflow.org:9000",
_16
"testnet": "access.devnet.nodes.onflow.org:9000"
_16
},
_16
"accounts": {
_16
"emulator-account": {
_16
"address": "f8d6e0586b0a20c7",
_16
"key": {
_16
"type": "file",
_16
"location": "./emulator.key"
_16
}
_16
}
_16
}
_16
}

info

The flow.json file is used to keep track of deployed contracts and accounts. More Information

Configuring FCL

Next, notice @onflow/fcl has been added to the package.json and the web application is ready to be run.

info

There are a lot of benefits to getting familiar with existing Flow CLI scaffolds. For example the hello-world scaffold already has fcl configuration settings to run on local emulator.

The hello-world web application comes with convenience npm commands to facilitate a quick start. The following command will preform:

  1. Start emulator
  2. Start dev wallet
  3. Start web app
info

Emulator is a local blockchain More Information. Emulator has all the features as testnet and mainnet blockchains


_10
npm run dev:local

Now that your app is running. FCL loads the configuration in config/fcl.ts This file contains configuration information for FCL, such as what Access Node and wallet discovery endpoint and which network to use (e.g. testnet or a local emulator).

  • accessNode.api key specifies the address of a Flow access node. There are publically available access nodes, but have to rate limit. Alternatively, applications might want to run an Observer node Run a Node.
  • discovery.wallet is an address that points to a service that lists FCL compatible wallets. Flow's FCL Discovery service is a service that FCL wallet providers can be added to, and be made 'discoverable' to any application that uses the discovery.wallet endpoint.

Also, notice that package.json uses NEXT_PUBLIC_FLOW_NETWORK=local for dev command, this is used to set the network in config/fcl.ts.

The main Next.js app component is located in pages/_app.tsx. It should import the config file config/fcl.ts already and should look like this:


_15
import '../styles/globals.css'
_15
import DefaultLayout from '../layouts/DefaultLayout'
_15
_15
// Import FCL config
_15
import '../config/fcl'
_15
_15
function MyApp({ Component, pageProps }) {
_15
return (
_15
<DefaultLayout>
_15
<Component {...pageProps} />
_15
</DefaultLayout>
_15
)
_15
}
_15
_15
export default MyApp

The main page for the Next.js app is located in pages/index.tsx. It should contain some basic UI and should look like this:


_35
import Head from 'next/head'
_35
import styles from '../styles/Home.module.css'
_35
import Links from '../components/Links'
_35
import Container from '../components/Container'
_35
import useCurrentUser from '../hooks/useCurrentUser'
_35
_35
export default function Home() {
_35
const { loggedIn } = useCurrentUser()
_35
_35
return (
_35
<div className={styles.container}>
_35
_35
<Head>
_35
<title>FCL Next Scaffold</title>
_35
<meta name="description" content="FCL Next Scaffold for the Flow Blockchain" />
_35
<link rel="icon" href="/favicon.ico" />
_35
</Head>
_35
_35
<main className={styles.main}>
_35
<h1 className={styles.title}>
_35
<a href="https://developers.flow.com/tools/fcl-js">FCL</a> Next Scaffold
_35
</h1>
_35
_35
<p className={styles.description}>
_35
For the Flow Blockchain
_35
</p>
_35
_35
{loggedIn && <Container />}
_35
_35
<Links />
_35
_35
</main>
_35
</div>
_35
)
_35
}

Now we're ready to start talking to Flow!

warning

The web app will run, but there are no contracts deployed to local emulator. This is a step in Query the Blockchain section.

Authenticate a User

Navigate to localhost:3000 in your browser.

User Login

Note: in the code above useCurrentUser is used to determine if there is a logged in user.

There are two methods to allow the user to login. fcl.logIn() or fcl.authenticate() More Information on, authenticate

In components/Navbar.tsx a button wires up the authentication method fcl.authenticate(). It is used to bring up the list of supported wallets. See below

Once authenticated, FCL uses a hook const user = useCurrentUser() to get the user data, when user is signed in user.loggedIn flag is true. For more information on the currentUser, read more here.


_33
import Head from 'next/head'
_33
import * as fcl from '@onflow/fcl'
_33
import useCurrentUser from '../hooks/useCurrentUser'
_33
import navbarStyles from '../styles/Navbar.module.css'
_33
import elementStyles from '../styles/Elements.module.css'
_33
_33
export default function Navbar() {
_33
const user = useCurrentUser()
_33
_33
return (
_33
<div className={navbarStyles.navbar}>
_33
{!user.loggedIn &&
_33
<button
_33
onClick={fcl.authenticate}
_33
className={elementStyles.button}>
_33
Log In With Wallet
_33
</button>
_33
}
_33
{user.loggedIn &&
_33
(
_33
<>
_33
<div className={navbarStyles.address}>{ user?.addr }</div>
_33
<button
_33
onClick={fcl.unauthenticate}
_33
className={elementStyles.button}>
_33
Log Out
_33
</button>
_33
</>
_33
)
_33
}
_33
</div>
_33
)
_33
}

You should now be able to log in or sign up users and unauthenticate them. Upon logging in or signing up your users will see a popup where they can choose between wallet providers. Choose the dev wallet to use the same account that deployed the HelloWorld contract, this is needed for mutation. Upon completing authentication, you'll see the component change and the user's wallet address appear on the screen if you've completed this properly.

info

More on wallets, Flow Core wallet is a Reference Flow wallet.

Deploy contracts

Hello World scaffold does come with a Cadence contract. You will notice HelloWorld.cdc has been deployed when running npm run dev:local. Look at hello-world package.json to see the commands that get run, flow dev deploys contracts to the emulator. In the flow.json make sure the emulator endpoint is correct. Look at the terminal the emulator is running,

Emulator Output

  1. Make sure the emulator is using the same port as gRPC and
  2. The deployment section of flow.json should look something like this:

_12
...
_12
"networks": {
_12
"emulator": "127.0.0.1:3569",
_12
...
_12
},
_12
"deployments": {
_12
"emulator": {
_12
"default": [
_12
"HelloWorld"
_12
]
_12
}
_12
}

Verify that flow.json updates with HelloWorld contract information, contracts has the HelloWorld contract and deployments shows that HelloWorld has been deployed.


_30
{
_30
"contracts": {
_30
"HelloWorld": "cadence/contracts/HelloWorld.cdc"
_30
},
_30
"networks": {
_30
"emulator": "127.0.0.1:3569",
_30
"mainnet": "access.mainnet.nodes.onflow.org:9000",
_30
"testnet": "access.devnet.nodes.onflow.org:9000"
_30
},
_30
"accounts": {
_30
"default": {
_30
"address": "01cf0e2f2f715450",
_30
"key": "..."
_30
},
_30
"emulator-account": {
_30
"address": "f8d6e0586b0a20c7",
_30
"key": {
_30
"type": "file",
_30
"location": "./emulator.key"
_30
}
_30
}
_30
},
_30
"deployments": {
_30
"emulator": {
_30
"default": [
_30
"HelloWorld"
_30
]
_30
}
_30
}
_30
}

For more information on deployments check Flow CLI documentation

Query the Blockchain

Now that all the pieces are in place, you can query the HelloWorld contract.

Navigate again to localhost:3000 in your browser.

Query HelloWorld Contract

The script that queries the Hello World contract is located hello-world/cadence/scripts/ReadHelloWorld.cdc


_10
import "HelloWorld"
_10
_10
access(all) fun main(): String {
_10
return HelloWorld.greeting
_10
}

In components/Container.tsx file, fcl.query is used to set the Cadence script and query the contract.


_10
_10
const queryChain = async () => {
_10
const res = await fcl.query({
_10
cadence: ReadHelloWorld
_10
})
_10
_10
setChainGreeting(res)
_10
}

It is that simple!

Mutate the Blockchain

Update the HelloWorld contract greeting. Notice that Greeting gets changed when the transaction gets processed.

Update contract

In components/Container.tsx file, the mutateGreeting method fcl.mutate sends UpdateHelloWorld cadence which triggers a transaction that the user signs.


_14
const mutateGreeting = async (event) => {
_14
event.preventDefault()
_14
_14
if (!userGreetingInput.length) {
_14
throw new Error('Please add a new greeting string.')
_14
}
_14
_14
const transactionId = await fcl.mutate({
_14
cadence: UpdateHelloWorld,
_14
args: (arg, t) => [arg(userGreetingInput, t.String)],
_14
})
_14
_14
setLastTransactionId(transactionId)
_14
}

More information

That's it! You now have Flow app that uses auth, query and mutate the chain. This is just the beginning. There is so much more to know. Next steps:

Cadence

FCL Scaffolds

Full Stack NFT Marketplace Example

More FCL