This repo help you set up basic Lens Protocol Nodejs App which uses Lens Protocol GraphQL API for fetching/mutating data on Lens Protocol, also setup your own REST API using Express.
First setup basic typescript nodejs application using basic-ts-express-app repo
- Creating a simple GET REST API for fetching data from Lens Protocol
- Setting up Prettier with ESLint
- Setting up Husky
- Creating a simple POST/DELETE REST API for mutating data from Lens Protocol
- Setting Up Codegen for Lens GraphQL
Simple GET REST API
In this simple example, we will fetch handle for hardcoded app address from Lens Protocol API
Creating aBase Client
using URQL for all sorts of
fetching related stuff from Lens Protocol.
Under utils/lens-protocol
folder create a base-client.ts
file
Note: Rationale behind using URQL
client can be understood from this
article 5 GraphQL clients for JavaScript and Node.js
Create a profile-route
route
// app.ts
app.use("/profile", profileRoutes);
// routes/profile.route.ts
import { Request, Response, NextFunction } from "express";
import baseClientUtil from "../utils/lens-protocol/base-client.util";
import getDefaultProfileGraphql from "../graphql/get-default-profile.graphql";
import { APP_ADDRESS } from "../config/env.config";
/**
* Get the handle.
*
* @param req - The request object.
* @param res - The response object.
* @param _next - The next function.
* @returns The handle object.
*/
export const getHandle = async (
req: Request,
res: Response,
_next: NextFunction
) => {
const response = await baseClientUtil
.query(getDefaultProfileGraphql, { address: APP_ADDRESS })
.toPromise();
res.status(200).json({
handle: response?.data?.defaultProfile.handle
});
};
Create profile.controller.ts
file
import { Request, Response, NextFunction } from "express";
import baseClientUtil from "../utils/lens-protocol/base-client.util";
import getDefaultProfileGraphql from "../graphql/get-default-profile.graphql";
import { APP_ADDRESS } from "../config/env.config";
/**
* Get the handle.
*
* @param req - The request object.
* @param res - The response object.
* @param _next - The next function.
* @returns The handle object.
*/
export const getHandle = async (
req: Request,
res: Response,
_next: NextFunction
) => {
const response = await baseClientUtil
.query(getDefaultProfileGraphql, { address: APP_ADDRESS })
.toPromise();
res.status(200).json({
handle: response?.data?.defaultProfile.handle
});
};
Create models & utility function as per the requirement.

Setup Prettier with ESLint
Refer this article to setup prettier 👉 How to use Prettier with ESLint and TypeScript in VSCode
Refer this article to setup ESLint 👉 How to use ESLint with TypeScript
You start getting unused error
once you run script npm run lint
in files like app.ts
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
res.status(404).json({ message: err.message });
});
As next is not used so ESLint throw unused error
, but if you remove next then you will not get others error.
To resolve this you add new rules in .eslintrc
{
"root": true,
"rules": {
"_comment": "Below rule help us ignore any unused variables error thrown by eslint",
"@typescript-eslint/no-unused-vars": [
"error",
{
"argsIgnorePattern": "^_",
"varsIgnorePattern": "^_",
"caughtErrorsIgnorePattern": "^_"
}
]
}
}
After this you always define any unused variable in your code, by starting variable name with underscore
Example:
app.use((err: Error, req: Request, res: Response, _next: NextFunction) => {
res.status(404).json({ message: err.message });
});
Setting Up Husky
Husky to prevent bad git commits and enforce code standards in your project.
To understand more about husky, refer to this article 👉 Enforcing Coding Conventions with Husky Pre-commit Hooks
Note: Above article setup is old so follow below steps to set up husky
npx husky-init && npm install
Note: Above command may not work in powershell, so try running it in cmd or git bash
npx husky set .husky/pre-commit "npm run prettier-format && npm run lint"
This adds script in .husky\pre-commit
, which will ensure your code is formatted and linted before committing
After this when ever anyone will try to commit then husky will run script npm run prettier-format && npm run lint
Referred resources
- If any file contains prettier then those will be fixed, and you need to commit that fixed code again.
- Issue related to linting will be reported, and you need fix then only you can commit the code
Note: For setting up Husky for project where are there are app/projects in sub-folders, follow this StackOverflow thread
Setting Up Environment Variables
npm install dotenv
Create a .env
file in the root of your project
Create a src\config\env.config.ts
file. We will use this file to get our environment variables.
This help reduce code duplication.
import dotenv from "dotenv";
dotenv.config();
export const APP_ADDRESS = process.env.APP_ADDRESS as string;
export const PRIVATE_KEY = process.env.PRIVATE_KEY as string;
Refer this article 👉 Node.js Everywhere with Environment Variables! for better understanding
Simple POST/DELETE REST API
In this simple example, we will be posting and deleting reaction for a post through Lens Protocol GraphQL API
Create a Authenticated Client
using URQL for all sorts of
mutation-related stuff from Lens Protocol.
Under utils/lens-protocol
folder create a authenticated-client.util.ts
file.
Create a user-action
route
// app.ts
app.use("/user-action", userActionRoute);
// routes/user-action.ts
import express from "express";
import {
addReaction,
removeReaction
} from "../controllers/user-action.controller";
const router = express.Router();
// POST /user-action/reaction
router.post("/reaction", addReaction);
// DELETE /user-action/reaction
router.delete("/reaction", removeReaction);
export default router;
Create user-action.controller.ts
file
import { Request, Response, NextFunction } from "express";
import { ReactionRequestBodyModel } from "../models/request-bodies/reaction.request-body.model";
import {
addReactionToAPost,
removeReactionFromAPost
} from "../utils/lens-protocol/update-reaction-for-post.util";
/**
* Adds a reaction to a post.
*
* @param req - The request object containing the publication ID and reaction.
* @param res - The response object.
* @param _next - The next function.
*/
export const addReaction = async (
req: Request<unknown, unknown, ReactionRequestBodyModel>,
res: Response,
_next: NextFunction
) => {
try {
// Call the function to add the reaction to the post
await addReactionToAPost(req.body.publicationId, req.body.reaction);
res.status(200).json({
message: "Reaction added successfully"
});
} catch (error) {
res.status(503).json({
message:
"Could not add reaction to publication id: " + req.body.publicationId
});
}
};
/**
* remove a reaction from a post.
*
* @param req - The request object containing the publication ID and reaction.
* @param res - The response object.
* @param _next - The next function.
*/
export const removeReaction = async (
req: Request<unknown, unknown, ReactionRequestBodyModel>,
res: Response,
_next: NextFunction
) => {
try {
// Call the function to remove the reaction from a post
await removeReactionFromAPost(req.body.publicationId, req.body.reaction);
res.status(200).json({
message: "Reaction removed successfully"
});
} catch (error) {
res.status(503).json({
message:
"Could not remove reaction from publication id: " +
req.body.publicationId
});
}
};
Create models & utility function as per the requirement.
POST

DELETE

- ChatGPT Thread on API structuring.
Setting Up Codegen for Lens GraphQL
Using codegen we will be able to make Lens Graphql responses type safe.npm i graphql
npm i -D typescript @graphql-codegen/cli
npm i @graphql-codegen/client-preset
npm i -D @parcel/watcher
to watch your code changes and codegen automatically- Add script
"codegen": "graphql-codegen --watch"
topackage.json
npm i
codegen.ts
file
import type { CodegenConfig } from "@graphql-codegen/cli";
const config: CodegenConfig = {
schema: "https://api-mumbai.lens.dev/",
documents: ["src/graphql/*.ts"], //from where to pick queries & mutations
ignoreNoDocuments: true, // for better experience with the watcher
generates: {
"./src/gql/": {
//where to put generated code
preset: "client",
plugins: []
}
}
};
export default config;
Alternatively
We can place directly place schema.grapgql
after downloading from https://api-mumbai.lens.dev/,
if there is any issue with url call.
This will also resolve typescript issue that might happen in file under src/graphql
const config: CodegenConfig = {
schema: "schema.graphql"
};
export default config;
Add src/gql
folder in .gitignore
& .eslintignore
as these are dev dependencies and can
be generated during development
Below is an example on how to use codegen
- In
src/graphql/get-default-profile-query.graphql.ts
file
import { graphql } from "../gql";
const getDefaultProfileByAddressQuery = graphql(/* GraphQL */ `
query defaultProfile($address: EthereumAddress!) {
defaultProfile(request: { ethereumAddress: $address }) {
id
name
isDefault
metadata
handle
picture {
... on MediaSet {
original {
url
}
}
}
ownedBy
}
}
`);
export default getDefaultProfileByAddressQuery;
- In
src/controllers/profile.controller.ts
file
import { Request, Response, NextFunction } from "express";
import baseClientUtil from "../utils/lens-protocol/base-client.util";
import { APP_ADDRESS } from "../config/env.config";
import getDefaultProfileByAddressQuery from "../graphql/get-default-profile-query.graphql";
/**
* Get the handle.
*
* @param req - The request object.
* @param res - The response object.
* @param _next - The next function.
* @returns The handle object.
*/
export const getHandle = async (
req: Request,
res: Response,
_next: NextFunction
) => {
const response = await baseClientUtil
.query(getDefaultProfileByAddressQuery, { address: APP_ADDRESS })
.toPromise();
res.status(200).json({
handle: response.data?.defaultProfile?.handle
});
};
- Run
npm run codegen
Here response
variable will contain all types that are there in a query with complete type safety
You might not get intellisense in some scenario like when UINION like MediaSet
are used
Like response.data?.defaultProfile?.picture?.original?.url
IDE will throw error
TS2339: Propert original does not exist on type
{ __typename?: "MediaSet" | undefined; original: { __typename?: "Media" | undefined; url: any; }; } | { __typename?: "NftImage" | undefined; }
Property  original does not exist on type  { __typename?: "NftImage" | undefined; }
To resolve this, you can write this in two way
- Long way
if (response.data?.defaultProfile?.picture) {
if (response.data.defaultProfile.picture.__typename === "MediaSet") {
url = response.data.defaultProfile.picture.original?.url;
} else if (response.data.defaultProfile.picture.__typename === "NftImage") {
// Handle NftImage accordingly
}
}
- Short way
(
response.data?.defaultProfile?.picture as {
__typename: "MediaSet";
original: { url: string };
}
)?.original?.url;
ChatGPT's solution thread: https://chat.openai.com/share/2ca275d8-20d7-469d-a335-4fd779b87c30
npm run build
to build your codenpm run start
to start your servernpm run prettier-watch
to format your code automatically
- Add more customizable rules in
.eslintrc
&.prettierrc
- Work on using schema url instead of downloaded schema file