Tutorials
1. Building a LINE Bot
This tutorial covers creating a fully-featured LINE Bot that responds to messages, follows, and postbacks.
How Webhook Works
→
LINE Platform
receives & forwards
→
Your Server
linekit processes
→
→
Step 1: Set up your project
mkdir my-line-bot && cd my-line-bot
npm init -y
npm install express @linekit/core @linekit/messaging @linekit/express
npm install -D typescript @types/node @types/express tsx
Step 2: Create the bot
import express from "express";
import { createLineApp } from "@linekit/core";
import { lineMiddleware } from "@linekit/express";
const app = express();
const config = {
channelSecret: process.env.CHANNEL_SECRET!,
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN!,
};
const line = createLineApp(config);
app.post("/webhook",
lineMiddleware(config),
line.router({
message: async (ctx) => {
const { message } = ctx.event;
if (message.type === "text") {
await ctx.replyText(`Echo: ${message.text}`);
} else if (message.type === "sticker") {
await ctx.reply({
type: "sticker",
packageId: message.packageId,
stickerId: message.stickerId,
});
} else if (message.type === "image") {
await ctx.replyText("Nice image!");
}
},
follow: async (ctx) => {
await ctx.replyText("Thanks for following! Send me a message.");
},
postback: async (ctx) => {
const data = ctx.event.postback.data;
await ctx.replyText(`You clicked: ${data}`);
},
}, {
onError: (error, ctx) => {
console.error(`Error for user ${ctx.userId}:`, error);
}
})
);
app.listen(3000, () => {
console.log("Bot running on http://localhost:3000");
});
Step 3: Run your bot
export CHANNEL_SECRET="your-channel-secret"
export CHANNEL_ACCESS_TOKEN="your-channel-access-token"
npx tsx src/index.ts
Tip: Use ngrok or similar tools to expose your local server for webhook testing:
ngrok http 3000
2. LINE Login Integration
Implement "Log in with LINE" in your web application with CSRF protection.
import {
login,
generateAuthUrl,
issueAccessToken,
LineLoginError
} from "@linekit/login";
const CHANNEL_ID = process.env.LINE_LOGIN_CHANNEL_ID!;
const CHANNEL_SECRET = process.env.LINE_LOGIN_CHANNEL_SECRET!;
const REDIRECT_URI = "https://example.com/callback";
export function getLoginUrl(session: any) {
const state = login.generateState();
const nonce = login.generateNonce();
session.lineLoginState = state;
session.lineLoginNonce = nonce;
return generateAuthUrl({
channelId: CHANNEL_ID,
redirectUri: REDIRECT_URI,
state,
nonce,
scope: "profile openid email",
});
}
export async function handleCallback(
code: string,
returnedState: string,
session: any
) {
if (!login.validateState(session.lineLoginState, returnedState)) {
throw new Error("Invalid state - possible CSRF attack");
}
try {
const tokens = await issueAccessToken(
CHANNEL_ID,
CHANNEL_SECRET,
code,
REDIRECT_URI
);
const user = await login.verify(tokens.id_token!, CHANNEL_ID);
return {
userId: user.sub,
name: user.name,
picture: user.picture,
email: user.email,
accessToken: tokens.access_token,
};
} catch (error) {
if (error instanceof LineLoginError) {
console.error(`Login failed: ${error.code}`);
}
throw error;
}
}
app.get("/login", (req, res) => {
const url = getLoginUrl(req.session);
res.redirect(url);
});
app.get("/callback", async (req, res) => {
const { code, state } = req.query;
const user = await handleCallback(code, state, req.session);
req.session.user = user;
res.redirect("/dashboard");
});
3. Sending Different Message Types
LINE supports various message types. Here's how to send each one.
import {
replyMessage,
pushMessage,
multicast
} from "@linekit/messaging";
const TOKEN = process.env.CHANNEL_ACCESS_TOKEN!;
await pushMessage(TOKEN, userId, [{
type: "text",
text: "Hello, World!"
}]);
await pushMessage(TOKEN, userId, [{
type: "text",
text: "Hello $ LINE emoji!",
emojis: [{
index: 6,
productId: "5ac1bfd5040ab15980c9b435",
emojiId: "001"
}]
}]);
await pushMessage(TOKEN, userId, [{
type: "sticker",
packageId: "446",
stickerId: "1988"
}]);
await pushMessage(TOKEN, userId, [{
type: "image",
originalContentUrl: "https://example.com/image.jpg",
previewImageUrl: "https://example.com/preview.jpg"
}]);
await pushMessage(TOKEN, userId, [{
type: "location",
title: "LINE Office",
address: "Tokyo, Japan",
latitude: 35.687574,
longitude: 139.72922
}]);
await pushMessage(TOKEN, userId, [{
type: "flex",
altText: "This is a Flex Message",
contents: {
type: "bubble",
body: {
type: "box",
layout: "vertical",
contents: [
{ type: "text", text: "Hello", weight: "bold", size: "xl" },
{ type: "text", text: "World", size: "md" }
]
}
}
}]);
await multicast(TOKEN, [userId1, userId2, userId3], [{
type: "text",
text: "Hello everyone!"
}]);
5. Error Handling
Properly handle errors from LINE API calls.
import { LineClientError } from "@linekit/messaging";
import { LineLoginError } from "@linekit/login";
try {
await pushMessage(TOKEN, userId, messages);
} catch (error) {
if (error instanceof LineClientError) {
console.error(`LINE API Error: ${error.message}`);
console.error(`Status: ${error.status}`);
console.error(`Details:`, error.data);
}
}
6. Marketing Push
Send messages to a large list of users with automatic chunking and rate limiting.
import { bulkMulticast } from "@linekit/messaging";
const userList = ["U1...", "U2...", ];
const results = await bulkMulticast(
TOKEN,
userList,
[{ type: "text", text: "Weekly Update!" }],
{
chunkSize: 500,
delayMs: 1000
}
);
const successCount = results.filter(r => r.success).reduce((acc, r) => acc + r.count, 0);
console.log(`Sent to ${successCount} users`);
7. LIFF App Management
Create and manage LIFF apps programmatically from your server.
import { LiffClient } from "@linekit/liff";
const client = new LiffClient({
channelAccessToken: process.env.CHANNEL_ACCESS_TOKEN
});
const { liffId } = await client.add({
view: {
type: "full",
url: "https://mysite.com/app"
}
});
console.log("Created LIFF App: https://liff.line.me/" + liffId);
console.error(`Details:`, error.data);
if (error.status === 400) {
} else if (error.status === 401) {
} else if (error.status === 429) {
}
}
}
try {
const user = await login.verify(idToken, channelId);
} catch (error) {
if (error instanceof LineLoginError) {
console.error(`Login Error: ${error.code}`);
if (error.code === "INVALID_PARAMETER") {
} else if (error.code === "VERIFICATION_FAILED") {
}
}
}
const router = line.router({
message: async (ctx) => {
}
}, {
onError: async (error, ctx) => {
console.error(`Error handling ${ctx.event.type}:`,
error);
}
});