Developer Quickstart
In this guide you'll learn how to build a full stack web application with Lens Protocol, Next.js, and Tailwind CSS.
Basic concepts
Lens Protocol allows developers to quickly and easily build web3 social applications.
As a developer you can either use the Lens deployment on Polygon or deploy the protocol yourself to any EVM compatible blockchain network.
In this tutorial you'll use the Lens API to fetch and render a social media feed, navigate to view an individual profile, and fetch and view the user's publications.
The Lens API can be tested at any time here using any of the GraphQL queries in the API docs.
Prerequisites
To complete this tutorial successfully, you must have Node.js installed on your machine.
Getting started
To get started we'll first create and configure the Next.js application.
To generate a new Next.js app, run the following command from your terminal:
npx create-next-app lens-app
ā Would you like to use TypeScript with this project? Yes
ā Would you like to use ESLint with this project? Yes
ā Would you like to use `src/` directory with this project? No
ā Would you like to use experimental `app/` directory with this project? Yes
ā What import alias would you like configured? ⦠@/*
Next, change into the new directory and install these dependencies for the GraphQL client:
npm install @apollo/client graphql
Next, update the tsconfig.json
and add the following to the compilerOptions
configuration:
"noImplicitAny": false,
Next, we'll install and configure TailwindCSS for styling:
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
Once Tailwind is installed and configured, open tailwind.config.js
and configure the content
array to include the paths to the template files in our app:
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
"./app/**/*.{js,ts,jsx,tsx}",
"./components/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}
Finally, open app/globals.css
and replace the contents of that file with these three lines of code:
@tailwind base;
@tailwind components;
@tailwind utilities;
Creating the API
Creating a basic GraphQL API is simple, we can do it in just a couple of lines of code. We'll also define the first GraphQL query we'll be using in our app.
Create a new file named api.ts
in the root of the new project and add the following code:
import { ApolloClient, InMemoryCache, gql } from '@apollo/client'
const API_URL = 'https://api.lens.dev'
/* create the API client */
export const client = new ApolloClient({
uri: API_URL,
cache: new InMemoryCache()
})
/* define a GraphQL query */
export const exploreProfiles = gql`
query ExploreProfiles {
exploreProfiles(request: { sortCriteria: MOST_FOLLOWERS }) {
items {
id
name
bio
handle
picture {
... on MediaSet {
original {
url
}
}
}
stats {
totalFollowers
}
}
}
}
`
The query we've defined here will fetch a list of profiles from Lens based on the profile sort criteria pass in, in our case MOST_FOLLOWERS
. You can view all of the sort criteria here, and experiment with other profile queries here.
Fetching and rendering profiles
We can now fetch data using the new API with a single line of code:
const response = await client.query({ query: exploreProfiles })
With that in mind, we'd like to fetch the profiles using this API, then render and style them in our app.
To do so, let's update app/page.tsx
in our app with the following code:
/* app/page.tsx */
'use client'
import { useEffect, useState } from 'react'
import { client, exploreProfiles } from '../api'
import Link from 'next/link'
export default function Home() {
/* create initial state to hold array of profiles */
const [profiles, setProfiles] = useState<any>([])
useEffect(() => {
fetchProfiles()
}, [])
async function fetchProfiles() {
try {
/* fetch profiles from Lens API */
let response = await client.query({ query: exploreProfiles })
/* loop over profiles, create properly formatted ipfs image links */
let profileData = await Promise.all(response.data.exploreProfiles.items.map(async profileInfo => {
let profile = { ...profileInfo }
let picture = profile.picture
if (picture && picture.original && picture.original.url) {
if (picture.original.url.startsWith('ipfs://')) {
let result = picture.original.url.substring(7, picture.original.url.length)
profile.avatarUrl = `http://lens.infura-ipfs.io/ipfs/${result}`
} else {
profile.avatarUrl = picture.original.url
}
}
return profile
}))
/* update the local state with the profiles array */
setProfiles(profileData)
} catch (err) {
console.log({ err })
}
}
return (
<div className='pt-20'>
<div className='flex flex-col justify-center items-center'>
<h1 className='text-5xl mb-6 font-bold'>Hello Lens šæ</h1>
{
profiles.map(profile => (
<div key={profile.id} className='w-2/3 shadow-md p-6 rounded-lg mb-8 flex flex-col items-center'>
<img className='w-48' src={profile.avatarUrl || 'https://picsum.photos/200'} />
<p className='text-xl text-center mt-6'>{profile.name}</p>
<p className='text-base text-gray-400 text-center mt-2'>{profile.bio}</p>
<Link href={`/profile/${profile.handle}`}>
<p className='cursor-pointer text-violet-600 text-lg font-medium text-center mt-2 mb-2'>{profile.handle}</p>
</Link>
<p className='text-pink-600 text-sm font-medium text-center'>{profile.stats.totalFollowers} followers</p>
</div>
))
}
</div>
</div>
)
}
Next, run the app:
npm run dev
When the app loads, you should see a list of Lens profiles with their image, name, bio, and handle.
š Congratulations! You've built your first Lens application!
Next, we'd like to be able to click on a user's handle and view more details about the user, like their latest posts.
Viewing an individual profile
For the individual profile view we'll need two new API calls:
- To fetch the profile details for the individual user
- To fetch the publications for the individual user
To enable this, open api.ts
and add the following two queries:
export const getProfile = gql`
query Profile($handle: Handle!) {
profile(request: { handle: $handle }) {
id
name
bio
picture {
... on MediaSet {
original {
url
}
}
}
handle
}
}
`
export const getPublications = gql`
query Publications($id: ProfileId!, $limit: LimitScalar) {
publications(request: {
profileId: $id,
publicationTypes: [POST],
limit: $limit
}) {
items {
__typename
... on Post {
...PostFields
}
}
}
}
fragment PostFields on Post {
id
metadata {
...MetadataOutputFields
}
}
fragment MetadataOutputFields on MetadataOutput {
content
}
`
In app/page.tsx
you'll notice that we're linking to the user's handle in the html:
<Link href={`/profile/${profile.handle}`}>
<p class='cursor-pointer text-violet-600 text-lg font-medium text-center mt-2 mb-2'>{profile.handle}</p>
</Link>
Right now, the /profile/${profile.handle}
route does not yet exist, so let's create it.
To do so, create a new folder in the app directory named profile
. Inside the profile
folder, create a new folder named [id]
. In the [id]
folder, create a new file named page.tsx
.
In this file add the following code:
/* app/profile/[id]/page.tsx */
'use client'
import { useState, useEffect } from 'react'
import { usePathname } from 'next/navigation';
import { client, getPublications, getProfile } from '../../../api'
export default function Profile() {
/* create initial state to hold user profile and array of publications */
const [profile, setProfile] = useState<any>()
const [publications, setPublications] = useState<any>([])
/* using the router we can get the lens handle from the route path */
const pathName = usePathname()
const handle = pathName?.split('/')[2]
useEffect(() => {
if (handle) {
fetchProfile()
}
}, [handle])
async function fetchProfile() {
try {
/* fetch the user profile using their handle */
const returnedProfile = await client.query({
query: getProfile,
variables: { handle }
})
const profileData = { ...returnedProfile.data.profile }
/* format their picture if it is not in the right format */
const picture = profileData.picture
if (picture && picture.original && picture.original.url) {
if (picture.original.url.startsWith('ipfs://')) {
let result = picture.original.url.substring(7, picture.original.url.length)
profileData.avatarUrl = `http://lens.infura-ipfs.io/ipfs/${result}`
} else {
profileData.avatarUrl = profileData.picture.original.url
}
}
setProfile(profileData)
/* fetch the user's publications from the Lens API and set them in the state */
const pubs = await client.query({
query: getPublications,
variables: {
id: profileData.id, limit: 50
}
})
setPublications(pubs.data.publications.items)
} catch (err) {
console.log('error fetching profile...', err)
}
}
if (!profile) return null
return (
<div className='pt-20'>
<div className='flex flex-col justify-center items-center'>
<img
className='w-64 rounded-full'
src={profile.avatarUrl}
/>
<p className='text-4xl mt-8 mb-8'>{profile.handle}</p>
<p className='text-center text-xl font-bold mt-2 mb-2 w-1/2'>{profile.bio}</p>
{
publications.map(pub => (
<div key={pub.id} className='shadow p-10 rounded mb-8 w-2/3'>
<p>{pub.metadata.content}</p>
</div>
))
}
</div>
</div>
)
}
Now, run the app:
npm run dev
When the app loads, you should be able to click on a user's handle and navigate to their profile. In this view, you'll see the user's profile along with a feed of their latest posts!
Next steps
Now that you've built your first basic application, it's time to explore more of the Lens API!
Consider diving into authentication, modules, or learning about gasless transactions and the dispatcher.
Also consider adding the following features to your new app:
- Following a user
- Searching for users
- Creating a post
Updated 28 days ago