Telegram Serverless lar deg kjøre backend-kode for boten din og Mini App-en din direkte på Telegrams infrastruktur — ingen servere å konfigurere, ingen containere å holde i live, ingen skalering å tenke på. Du skriver enkel JavaScript-modulkode, distribuerer dem med en enkelt kommando, og Telegram kjører dem i en rask, isolert V8-sandkasse som ligger rett ved siden av Bot API-et og en innebygd database.
Hvis du noen gang har koblet en bot til en VPS, en skyfunksjon eller et vertspanel bare for å svare på et /start-oppkall, er dette delen du ikke lenger trenger å gjøre.
Hvorfor serverless
En Telegram-bot er i bunn og grunn et program som reagerer på oppdateringer. Tradisjonelt måtte du hoste det programmet et sted som alltid er på, tilgjengelig og sikkert — og deretter holde det slik. Telegram Serverless fjerner det laget fullstendig:
- Ingen infrastruktur. Det er ingen maskin å leie, patche eller overvåke. Koden din kjører på forespørsel og skalerer automatisk med boten din.
- Legitimasjon inkludert. Telegram Bot API, en SQLite-basert database og utgående HTTP er tilgjengelig for hver modul rett ut av boksen — ingenting å installere, ingen legitimasjon å koble til.
- Rask, isolert kjøring. Hver påkalling kjører i en lettvektig V8-isolat, nær Telegrams egne systemer, slik at kall til Bot API-et og databasen din er raske og pålitelige.
- En ekte utvikler-arbeidsflyt. Et prosjekt ligger i en mappe på maskinen din under versjonskontroll. Du redigerer filer, ser nøyaktig hva som endret seg, distribuerer atomisk og ruller databaseskjemaet ditt fremover med gjennomgåtte migrasjoner — slik du allerede jobber med alt annet.
Den mentale modellen: Hvordan prosjektet fungerer
Du jobber på tre steder, og de mapper rent til hverandre:
| Hvor | Hva som ligger der |
|---|---|
| Prosjektmappen din | JavaScript-moduler — skjema, delt kode, oppdateringshåndterere, Mini App-endepunkter — og, hvis du har en, Mini App-ens front-end |
| Skyen | Den distribuerte kopien av disse modulene, botens database og Mini App-ens filer |
tgcloud CLI-en |
Broen — den viser deg forskjeller og synkroniserer dem |
Du SSH-er aldri inn i noe. Du redigerer filer lokalt, kjører npx tgcloud push, og plattformen tar seg av resten. Botens trafikk håndteres av de distribuerte modulene; databasen din består mellom påkallinger.
Et prosjekt har bare fire typer kode, alt inne i én tgcloud/-mappe:
tgcloud/
├─ endpoints/ # funksjoner din Mini App kaller
├─ handlers/ # funksjoner som reagerer på Telegram-oppdateringstyper
├─ lib/ # delt kode du importerer fra hvor som helst
└─ schema.js # databasetabellene dine
Når en oppdatering ankommer — en melding, et knappetrykk, et inline-oppkall — ruter Telegram det til den matchende håndtereren (handlers/message.js, handlers/callback_query.js, …) og kaller dens standard eksport. Den funksjonen snakker med Bot API-et og databasen gjennom SDK-et, og returnerer. Det er hele loopen. En oppdatering uten matchende håndterer ignoreres ganske enkelt, så du legger bare til håndtererene du trenger.
En Mini App er valgfri. Hvis boten din har en, ligger front-enden i samme prosjekt, ved siden av tgcloud/. Serverless hoster build-outputen og kjører endpoints/-ene den kaller. Se
Mini App front-end
.
Rask demo
Her er en komplett, fungerende demobot. Den svarer på hver melding og husker hvor mange den har sett fra hver chat.
tgcloud/schema.js
import { table, integer } from 'sdk/db';
export const counters = table('counters', {
chatId: integer('chat_id').primaryKey() ,
seen: integer('seen').notNull().default(0) ,
});
tgcloud/handlers/message.js
import { api, db } from 'sdk';
import { sql } from 'sdk/db';
import { counters } from '../schema.js';
export default async function (message) {
const chatId = message.chat.id;
// Sett inn telleren, eller øk den hvis denne chatten allerede har en — og få den
// resulterende raden tilbake i samme instruksjon via .returning().
const [row] = await db.insert(counters)
.values({ chatId, seen: 1 })
.onConflictDoUpdate({
target: counters.chatId,
set: { seen: sql`${counters.seen} + 1` },
})
.returning()
.run();
await api.sendMessage({
chat_id: chatId,
text: `Hei! Jeg har sett ${row.seen} melding(er) fra deg.`,
});
}
Distribuer den:
npx tgcloud push # last opp modulene
npx tgcloud migrate # opprett `counters`-tabellen
Det er en live bot med vedvarende tilstand og ingen server. Alt i den — api, db, table() DSL-en — er beskrevet i seksjonene nedenfor.
Serverless er en generell backend for Telegram-boter og Mini Apps, ikke en mal for én type app. Den er ideell for:
- Samtalende AI-boter som trenger å lagre tilstand per bruker i en database.
- Mini App-backender som lagrer brukerdata og serverer dynamisk innhold.
- Spill og verktøy — inkludert ledertavler, quizer og mer.
- Automatiseringer og integrasjoner som kaller tredjeparts HTTP-API-er og dytter resultater inn i chatter.
Komme i gang
Denne gjennomgangen tar deg fra en tom mappe til en live bot som svarer på meldinger og lagrer data. Den forutsetter at du har
Node.js
18 eller nyere installert og en bot registrert hos
@BotFather
. Innen du er ferdig, vil du ha brukt hver kommando du trenger til daglig: push, migrate, run og status.
Før noe annet, slå på Serverless. I @BotFather , åpne boten din → Serverless og slå den på. Det aktiverer funksjonen for denne boten og låser opp CLI-tilgangstoken, håndterere, bibliotek og database.
1. Opprett et prosjekt
Den raskeste måten å starte på er prosjektoppretteren, som setter opp et prosjekt og installerer CLI-en i det:
npm create @tgcloud/bot example_bot
cd example_bot
Argumentet er målmappen: send inn . for å sette opp i gjeldende mappe, eller en hvilken som helst bane. Det fungerer også i en eksisterende mappe og overskriver aldri filer du allerede har.
Dette gir deg et prosjekt som er klart til å redigeres:
example_bot/
├─ tgcloud/ # alt plattformen kjører
│ ├─ handlers/
│ │ └─ message.js # en start-melding-håndterer (ekkoer tekst tilbake)
│ ├─ lib/ # dine delte moduler går hit (tom for å starte)
│ └─ schema.js # databasetabellene dine
├─ docs/
│ └─ tgcloud-sdk.md # SDK-referanse (for deg og AI-verktøyene dine)
├─ AGENTS.md # orientering for AI-kodeassistenter
├─ package.json
└─ tgcloud.jsonc # prosjektkonfigurasjon
De opprettede filene er selvforklarende — hver av dem inneholder kommenterte eksempler på hva du kan gjøre videre.
CLI-en installeres i prosjektet som en lokal dev-avhengighet, så du kjører den med npx tgcloud <kommando> (npx finner kopien i prosjektets node_modules), eller gjennom npm run-snarveiene scaffolden legger til i package.json (npm run deploy, npm run status). Som standard er det ingen global tgcloud i PATH-en din.
Du kan også installere den globalt — npm install -g @tgcloud/cli — hvis du heller vil skrive en bar tgcloud hvor som helst. Det er hendig for å kjøre tgcloud init i en hvilken som helst tom mappe, og det er det shell-tab-fullføring trenger. Uansett får du det samme prosjektet.
Bygger du en Mini App? Start fra front-enden i stedet. Opprett den med ditt vanlige verktøy, og kjør deretter prosjektoppretteren i den mappen. Se Mini App front-end .
2. Koble til boten din
Hvert prosjekt er knyttet til én bot. Koble dem sammen med login, som ber om CLI-tilgangstokenet ditt (
@BotFather
→ boten din → Serverless → CLI Access → Access token — et eget token fra
botens API-token
) og lagrer det lokalt:
npx tgcloud login
Tokenet har formatet app<id>:<secret>. CLI-en lagrer det i .tgcloud/, som er git-ignorert, og skriver aldri ut hemmelighetsdelen. Login er den eneste gangen du blir bedt om det — se
Autentisering
for hvordan tokens løses i CI.
3. Se deg rundt
To kommandoer forteller deg statusen til enhver tid, begge fullstendig offline:
npx tgcloud status # hva har endret seg lokalt vs. den distribuerte kopien
npx tgcloud diff # linje-for-linje-endringene
Rett etter init er alt nytt og ingenting er distribuert ennå. status viser startfilene som venter på å bli lastet opp.
4. Distribuer
Send modulene dine til skyen:
npx tgcloud push
push laster opp hver endret modul i én atomisk batch og oppdaterer den lokale oversikten din over hva skyen nå inneholder. Boten din er live: åpne den i Telegram og send en melding — starthåndtereren ekkoer den tilbake.
Distribusjon rører aldri databasen din. Å pushe kode og endre databaseskjemaet er bevisst separate steg, så en kodedistribusjon kan aldri overraske deg med en datamigrasjon. Det er det neste steg er til for.
5. Legg til en databasetabell
La oss få boten til å huske noe. Åpne tgcloud/schema.js og deklarer en tabell:
import { table, integer, text, sql } from 'sdk/db';
export const messages = table('messages', {
id: integer('id').primaryKey({ autoIncrement: true }) ,
chatId: integer('chat_id').notNull() ,
text: text('text') ,
created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
});
Distribuer skjemaet, og bruk det deretter på databasen:
npx tgcloud push # laster opp det nye skjemaet
npx tgcloud migrate # oppretter `messages`-tabellen
push rapporterer at skjemaet er ute av synk og viser deg endringen som venter, men bruker ingenting. migrate guider deg gjennom endringen og, etter din bekreftelse, oppretter tabellen. Denne to-trinns modellen — og hva som skjer med mer risikable endringer som drops — er dekket i
Migrasjoner
.
6. Lagre og lese data
Bruk nå tabellen fra håndtereren din. Rediger tgcloud/handlers/message.js:
import { api, db } from 'sdk';
import { eq } from 'sdk/db';
import { messages } from '../schema.js';
export default async function (message) {
// Lagre denne meldingen.
await db.insert(messages)
.values({ chatId: message.chat.id, text: message.text })
.run();
// Tell hvor mange vi har lagret for denne chatten.
const count = await db.$count(messages, eq(messages.chatId, message.chat.id));
await api.sendMessage({
chat_id: message.chat.id,
text: `Lagret. Det er ${count} melding(er) fra denne chatten så langt.`,
});
}
Distribuer den oppdaterte håndtereren med npx tgcloud push, send boten noen meldinger og se tallet stige. Databasen består mellom påkallinger — det er botens minne.
7. Test uten å distribuere
Du trenger ikke å distribuere for å prøve en endring. npx tgcloud run utfører en håndterer på plattformen ved hjelp av dine lokale filer, uten å publisere dem:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hello" }'
Argumentet er nyttelasten håndtereren din mottar — for handlers/message, en
Melding
— skrevet i
JSON5
(så du kan hoppe over å sitere nøkler). Kommandoen skriver ut alt håndtereren logget med console.*, returverdien og hvor lang tid det tok. Dette er den tetteste loopen for å iterere på logikk — ingen distribusjon, ingen venting på en ekte melding.
8. Hold deg synkronisert
Mens du jobber, holder en håndfull kommandoer det lokale prosjektet og skyen på linje: npx tgcloud status viser hva som endret seg, npx tgcloud push distribuerer, npx tgcloud pull bringer det lokale prosjektet ditt på linje med skyen, npx tgcloud fetch sjekker hva som er distribuert uten å røre filene dine, og npx tgcloud reset forkaster lokale endringer.
Hvis to personer (eller to maskiner) distribuerer til samme bot, oppdager plattformen konflikten og
pushstopper for å la degpullførst — du kan aldri lydløst overskrive andres arbeid. Se Holde seg synkronisert .
Bygge med AI
Foretrekker du å bygge med en AI-assistent — eller er den eneste koderen på teamet ditt en AI? Du kan fortsatt sende ut en bot. Vi har tatt et første steg for å få en AI-agent til å føle seg hjemme i et prosjekt: hver nye scaffoldes med en AGENTS.md og en docs/tgcloud-sdk.md-referanse som agentiske kodeverktøy leser automatisk.
Sammen med et lite, selvstendig runtime — ett SDK, ingen npm-pakker å styre med — gir det assistenten en flyvende start på konvensjonene generisk kodegenerering pleier å gå glipp av her: relative importer med .js, ingen fremmednøkler, hvert db-kall er async, én håndterer per oppdateringstype, og to-trinns push/migrate-flyten.
Prøv det:
npm create @tgcloud/bot my-bot
cd my-bot
opencode # eller Claude Code, Cursor, … — enhver agent som leser AGENTS.md
Så bare spør, på vanlig språk:
Skriv en bot som husker hver persons huskeliste — legg til et element når de sender tekst, og vis hele listen når de sender /list.
Assistenten redigerer schema.js og håndtererene dine for deg; du går gjennom, tester en endring umiddelbart med npx tgcloud run, og går deretter live med npx tgcloud push og npx tgcloud migrate. AGENTS.md er en del av prosjektet ditt — rediger den etter hvert som boten vokser, slik at veiledningen forblir nøyaktig.
På farten med BotFather
Bare telefonen tilgjengelig? Hele prosjektet lever i @BotFather også — åpne boten din → Serverless og du får alt CLI-en administrerer, på en berøringsskjerm:
- Håndterere — opprett, rediger og test-kjør oppdateringshåndterere; BotFather holder webhook-en i synk med håndtererene du har (det samme In synk / Out of synk som CLI-en rapporterer).
- Endepunkter — funksjonene Mini App-en din kaller.
- Bibliotek — dine delte
lib/-moduler. - Database — rediger
schema.jsi samme Drizzle-lignende syntaks, gå gjennom ventende endringer og bruk dem; Lagre distribuerer. - Mini App-filer — front-enden brukerne dine åpner i Telegram.
- CLI-tilgang — hent CLI-tilgangstokenet her når du er tilbake ved et tastatur.
Det er ett og samme skyprosjekt, så du kan starte en håndterer på telefonen din og npx tgcloud pull den til laptopen din senere — ingenting er knyttet til én enkelt klient. Å kjøre en håndterer viser til og med konsoll-utdataene rett i chatten, akkurat som
npx tgcloud run
.
Prosjekter og moduler
Et serverless-prosjekt er en vanlig mappe under versjonskontroll. Alt plattformen kjører ligger i én mappe, tgcloud/; resten av prosjektet er ditt — en mini-app front-end, konfigurasjon, dokumenter. Det er ingen build-steg for boten, ingen node_modules ved kjøretid, og ingen server-inngangspunkt.
Anatomi av et prosjekt
example_bot/
├─ tgcloud/ # den distribuerte delen — ingenting annet er
│ ├─ handlers/ # oppdateringshåndterere — flate, kun ett nivå
│ │ ├─ message.js
│ │ └─ callback_query.js
│ ├─ endpoints/ # funksjoner din Mini App kaller — flate, kun ett nivå
│ │ └─ getProfile.js
│ ├─ lib/ # delte moduler; undermapper tillatt
│ │ ├─ reply.js
│ │ └─ internal/util.js
│ └─ schema.js # databaseskjema — én fil
└─ .tgcloud/ # CLI-tilstand, git-ignorert
Kun .js-filer inne i tgcloud/ — schema.js, lib/, handlers/, endpoints/ — blir distribuert. Alt annet — Markdown, konfigurasjonsfiler, .tgcloud/-mappen, alt utenfor tgcloud/ — blir værende på maskinen din. Modulnavn er relative til tgcloud/. Filen tgcloud/handlers/message.js er modulen handlers/message, og det er navnet npx tgcloud run bruker.
Oppgraderer fra et eldre prosjekt? Prosjekter opprettet før CLI 0.2.0 beholdt
schema.js,lib/oghandlers/i prosjektroten. Kjørnpx tgcloud upgradeén gang for å flytte dem inn itgcloud/.
schema.js — databasen din. Den deklarerer tabeller som navngitte eksporter ved bruk av skjema-DSL-en og ligger i tgcloud/ som en enkelt fil. Den distribueres som enhver annen modul, men å distribuere den endrer aldri databasen — skjemaendringer brukes separat med npx tgcloud migrate. Se
Databasen
.
lib/ — delt kode, alt du vil gjenbruke på tvers av håndterere: rene hjelpere, database-tilgangslag, formatering, integrasjoner med eksterne tjenester. lib/ er den eneste mappen som kan inneholde undermapper (lib/internal/util.js, lib/payments/stripe.js), slik at du kan organisere en større kodebase slik du vil. Moduler i lib/ blir aldri kalt direkte av plattformen; de eksisterer for å importeres av håndterere og av hverandre.
handlers/ — inngangspunktene til boten din. Hver fil tilsvarer én Telegram-oppdateringstype, og plattformen ruter hver innkommende oppdatering til den matchende håndtereren:
| Fil | Håndterer |
|---|---|
handlers/message.js |
Nye innkommende meldinger |
handlers/inline_query.js |
Nye innkommende inline spørringer |
handlers/callback_query.js |
Nye innkommende callback-spørringer |
| … | enhver annen Bot API-oppdateringstype |
handlers/ er flat — ingen undermapper. En håndterers export default er funksjonen plattformen kaller (se
Håndterere
).
En oppdateringstype håndteres bare hvis håndtererfilen eksisterer og ikke er tom. Hvis det ikke finnes noen handlers/<type>.js — eller filen er tom — ignoreres oppdateringer av den typen, og plattformen kjører ingenting for dem. Så behold bare håndtererene du faktisk trenger: hver du legger til er en annen oppdateringstype boten din våkner for å behandle, og å utelate resten betyr at Telegram ikke spinner opp koden din for oppdateringer du uansett bare ville kastet.
For å opprette en ny håndterer, kjør
npx tgcloud add handlers/<type>.
endpoints/ — server-siden av din Mini App. Hver fil er en funksjon din Mini App kaller ved navn, så endpoints/getProfile.js svarer på POST /api/getProfile. Plattformen sjekker at kallet kommer fra en Telegram-bruker inne i din Mini App før koden din kjører, og gir deg denne brukeren. endpoints/ er flat, som handlers/. Se
Endepunkter
.
.tgcloud/ — maskin-lokal tilstand administrert utelukkende av CLI-en, inkludert din lagrede legitimasjon. Den er git-ignorert, og du bør aldri lese fra eller skrive til den for hånd — bruk CLI-kommandoene i stedet.
Modulsystemet
Ved kjøretid kan en modul se nøyaktig to ting: plattformens SDK og de andre modulene i prosjektet ditt. Det finnes ingen npm-pakker, ingen filsystem og ikke noe nettverk bortsett fra gjennom SDK-ens fetch.
Importer dine egne moduler slik du ville gjort i et hvilket som helst JavaScript-prosjekt — med relativ bane, med .js-utvidelsen — og SDK-et ved navnet sitt:
// tgcloud/handlers/message.js
import { users } from '../schema.js'; // skjema-modulen
import { addItem } from '../lib/cart.js'; // en lib-modul
import { format } from '../lib/internal/fmt.js'; // nøstet lib-modul
import { db, api, fetch } from 'sdk'; // plattformens SDK
// tgcloud/lib/cart.js
import { format } from './internal/fmt.js'; // en nabo
import { users } from '../schema.js';
En relativ import må slutte med .js og kan ikke føre utenfor tgcloud/:
import { users } from '../schema'; // legg til .js
import config from '../../config.js'; // utenfor tgcloud/
import { z } from 'zod'; // ingen npm-pakker
Hver modul har også et navn — banen inne i tgcloud/ uten utvidelsen, som schema, lib/cart eller handlers/message. Det navnet er hva npx tgcloud run og CLI-ens utdata bruker, og en modul kan importeres ved det også (fra 'lib/cart', ingen utvidelse). Foretrekk relative importer i koden din. Det er det editoren din forstår — gå-til-definisjon, autofullfør, gi nytt navn.
Det som er tilgjengelig ved kjøretid er nøyaktig to ting: sdk og dens undermoduler (sdk/db, sdk/api, sdk/fetch) — hele plattformoverflaten, se
SDK-et
— og dine egne moduler i tgcloud/. Det er den komplette listen. Hvis koden din importerer noe annet, vil det ikke løses. Denne begrensningen er det som holder moduler raske å laste og trygge å kjøre.
Håndterere
En håndterer er en modul i handlers/ hvis standard eksport plattformen kaller når en matchende oppdatering ankommer.
// handlers/message.js
import { api } from 'sdk';
export default async function (message) {
await api.sendMessage({
chat_id: message.chat.id,
text: `Du sa: ${message.text ?? '(ingen tekst)'}`,
});
}
En håndterer mottar oppdateringens nyttelast som sitt argument — plattformen pakker ut Telegram
Oppdateringen
for deg. handlers/message.js får
Meldingen
(dvs. update.message); handlers/callback_query.js får
CallbackQuery
; og så videre. Håndtererens andre argument er et per-påkalling kontekstobjekt, ctx. Det bærer den rå Update-en som ctx.update — bruk den når du trenger noe utenfor nyttelasten, som update_id.
En håndterer kan være async (vanligvis er den det) og kan returnere en verdi. Den når Bot API-et, databasen og utgående HTTP gjennom SDK-et.
Du trenger ikke en ekte oppdatering for å teste en håndterer. npx tgcloud run utfører den på plattformen med nyttelasten du oppgir og din nåværende lokale kode:
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Argumentet er nyttelasten — det samme objektet håndtereren din mottar — i JSON5. For å oppgi håndtererens ctx (dets andre argument), legg til --ctx, f.eks. --ctx '{ update: { update_id: 1 } }'. Dette kjøres mot dine lokale filer, slik at du kan prøve endringer før du distribuerer dem. Se
run
.
Endepunkter
Et endepunkt er en modul i endpoints/ som Mini App-en din kaller — stedet for alt front-enden ikke kan gjøre på egen hånd, som å lese og skrive til databasen, kalle Bot API-et eller snakke med eksterne tjenester med dine hemmeligheter.
// endpoints/getProfile.js
import { db, EndpointError } from 'sdk';
import { eq } from 'sdk/db';
import { profiles } from '../schema.js';
export default async function (input, ctx) {
const user = ctx.initData.user;
const profile = await db.select().from(profiles).where(eq(profiles.userId, user.id)).get();
if (!profile) throw new EndpointError('Profil ikke funnet', { userId: user.id });
return profile;
}
Det første argumentet, input, er JSON-objektet Mini App-en sendte. Det andre, ctx, bærer ctx.initData — Mini App-ens
init-data
(user, chat, start_param, auth_date, …), allerede verifisert av plattformen. Den har samme form som Telegram.WebApp.initDataUnsafe på klienten, men her kan du stole på den. Et kall uten gyldig init-data avvises med 401 før koden din kjører, så ctx.initData.user er virkelig personen som åpnet Mini App-en din. Endepunkter kan bare kalles på denne måten — fra botens Mini App, av en Telegram-bruker — så det er ingen offentlig API å beskytte og ingen pålogging å bygge.
Alt endepunktet returnerer sendes tilbake som JSON. For å nekte et kall, throw new EndpointError(beskrivelse, parametere?). Mini App-en får beskrivelsen og parameters-objektet slik de er. Hvis du trenger å forgrene deg basert på typen feil i front-enden, legg inn en kode i parameters — { code: 'OUT_OF_STOCK' }. Enhver annen unntakstilstand rapporteres som en generisk intern feil, og den virkelige meldingen går kun til loggene dine, så interne detaljer lekker aldri til klienten.
Kalle et endepunkt fra Mini App-en
Mini App-en kaller et endepunkt ved navn med Telegram.WebApp.Serverless.call fra det offisielle
Mini Apps SDK
, telegram-web-app.js — skriptet Mini App-en din allerede inkluderer. Det sender init-dataen for deg og ringer tilbake med en feil eller endepunktets resultat:
Telegram.WebApp.Serverless.call('getProfile', { lang: 'no' }, function (err, profile) {
if (err) {
// err.message — beskrivelsen; err.parameters — hva din EndpointError bar med seg
if (err.type === 'ENDPOINT_ERROR') showMessage(err.message);
return;
}
render(profile);
});
Input-en er valgfri og må være et vanlig objekt. Uten den er kallet Serverless.call('getProfile', callback).
err.type er 'ENDPOINT_ERROR' når endepunktet ditt kastet en EndpointError. Enhver annen verdi betyr at plattformen avviste kallet eller endepunktet feilet uventet, for eksempel 'UNAUTHORIZED' når init-data mangler eller er ugyldig.
Endepunktnavn er som JavaScript-identifikatorer — bokstaver, sifre og _, som ikke starter med et siffer (getProfile, place_order). Opprett ett med
npx tgcloud add endpoints/<navn>
, og prøv det uten Mini App-en med
run
, ved å sende inn init-data selv:
npx tgcloud run endpoints/getProfile '{}' --ctx '{ initData: { user: { id: 1, first_name: "Ann" } } }'
Hva blir distribuert
Når du npx tgcloud push, samler CLI-en hver .js-fil under schema.js, lib/, handlers/ og endpoints/, og sender det nøyaktige settet som prosjektets modultilstand — sammen med Mini App-ens build-mappe, hvis tgcloud.jsonc navngir en (se nedenfor). Alt som er tilstede i skyen, men fraværende fra prosjektet ditt, fjernes, så den distribuerte tilstanden speiler alltid mappen din — inkludert slettinger. Ingenting utenfor tgcloud/ distribueres som kode — resten av prosjektroten er din. Inne i tgcloud/ blir en .js-fil som ikke er på en av disse lokasjonene (tgcloud/util.js, tgcloud/helpers/x.js) flagget, slik at den ikke lydløst blir værende udistribuert. Markdown, dotfiles og .tgcloud/ distribueres aldri.
Mini App front-end
Prosjektroten er din, og det er det naturlige stedet for Mini App-ens front-end — bygget med React, Vue, Svelte eller ren HTML, av Vite eller en hvilken som helst annen bundler. Serverless hoster build-outputen ved siden av boten din. Én push distribuerer koden og de statiske filene sammen, og appen serveres på https://app<app_id>.tgcloud.ai/ (push skriver ut den nøyaktige adressen — bruk den som Mini App-ens URL i
@BotFather
).
Rapportere misbruk. Mini Apps på tgcloud.ai publiseres av uavhengige utviklere. For å rapportere ulovlig innhold eller misbruk på et
app<app_id>.tgcloud.ai-subdomene, send en e-post til [email protected] .
For å starte, opprett front-enden først, med hvilket verktøy du vil, og legg deretter til Serverless på toppen av den:
npm create vite@latest example_app # eller create-vue, …
cd example_app
npm create @tgcloud/bot .
Prosjektoppretteren overskriver aldri filene dine. Den legger til tgcloud/-mappen ved siden av front-enden, gjenkjenner build-mappen og legger til et npm run deploy-skript som bygger appen og distribuerer den sammen med boten.
Det som hostes settes i tgcloud.jsonc, prosjektets konfigurasjonsfil i prosjektroten:
{
"$schema": "./node_modules/@tgcloud/cli/schema/tgcloud.json",
"static": {
"source": "dist",
"spa": true,
"immutable": ["/assets/*"],
"headers": [{ "path": "/*", "set": { "Referrer-Policy": "no-referrer" } }],
"redirects": [{ "from": "/old", "to": "/new", "status": 301 }]
}
}
Kun "source" er påkrevd. Det er build-mappen, relativ til prosjektroten. De andre nøklene i "static" konfigurerer hvordan den serveres (nedenfor). init oppretter tgcloud.jsonc for deg. Når den gjenkjenner front-enden, fyller den også inn "source":
| Front-end | init setter "source" til |
|---|---|
| Vite — React, Vue, Svelte, Solid, … | dist |
| Next.js, Nuxt, SvelteKit, React Router, TanStack Start, Angular | ingenting — konfigurer en statisk eksport først, sett deretter "source" til mappen selv |
Med alle andre oppsett, sett "source" til build-mappen din selv.
$schema-linjen gir editoren din autofullføring og validering for hver nøkkel. Filen er JSON med kommentarer og etterfølgende komma tillatt. Hvis et annet verktøy må lese den som ren JSON, kall den tgcloud.json i stedet — da må den være streng JSON.
Bygg før du pusher. push laster opp mappen nøyaktig slik den er på disken — bortsett fra filer og mapper hvis navn starter med et punktum, som hoppes over — og kjører aldri bygget ditt, så en ren npx tgcloud push etter å ha redigert front-enden ville distribuert det forrige bygget. Bruk npm run deploy (den kjører build-skriptet ditt, deretter tgcloud push), eller bygg selv først.
Serveringsalternativene:
spa— serverindex.htmlfor baner som ikke eksisterer og ikke har filutvidelse, slik at en klient-side ruter fungerer ved reload. Baner med utvidelse får fortsatt404.immutable— mønstre for innholdshashed-filer (Vite’s/assets/*) som nettlesere kan cache i et år. Bruk det kun for innholdshashed-filer. Enhver annen fil som matches her ville forbli utdatert i nettlesere i et år, og det er ingen måte å hente den tilbake på.headers— response headers for banene en regel matcher (en nøyaktig bane, eller et prefiks som slutter på/*). Ennull-verdi fjerner en header.redirects— omdirigeringsregler, hvor det første treffet vinner. En eksisterende fil slår alltid en regel.statuser301med mindre du setter den."static": "dist"— en snarvei for{ "source": "dist" }når du ikke trenger noe annet."static": false— fjern det distribuerte nettstedet ved neste push. Å slette nøkkelen i stedet gjør bare at dette prosjektet slutter å administrere statiske filer, og det som er distribuert blir værende.
Nettstedet serveres fra roten av sin egen vert, så bundlerens standard base (/) er korrekt. Telegram Web åpner Mini Apps i en iframe, så ikke legg til X-Frame-Options eller en restriktiv frame-ancestors-policy.
Den samme verten svarer på
endepunktene
dine under /api/ — det er dit Telegram.WebApp.Serverless.call('getProfile', …) går. Ikke legg egne filer under /api/ i build-mappen.
Databasen
Hver bot får sin egen database — et SQLite-basert lager som består mellom påkallinger og er tilgjengelig for hver modul gjennom db. Du beskriver tabellene dine i schema.js med en liten, typet DSL; du leser og skriver dem med en flytende spørringsbygger; og du utvikler dem med gjennomgåtte migrasjoner.
Kjenner du Drizzle ORM ? Da vet du allerede hvordan du snakker med databasen her. Skjema-DSL-en og spørringsbyggeren følger Drizzle — kolonnebyggerne,
select().from().where(), operatørene,onConflictDoUpdate,.returning()ogsql-taggen oppfører seg alle slik du forventer, så det å lese og skrive data er det kjente API-et du allerede bruker. Du importerer det bare frasdk/db, og et par plattformspesifikke detaljer (spesielt ingen fremmednøkler ) blir påpekt der de dukker opp.
Deklarere tabeller
Tabeller er navngitte eksporter i schema.js. Å kalle table() bygger en beskrivelse ved lastetid (den rører ikke databasen); plattformen oppdager de eksporterte tabellene når du distribuerer schema.js og migrerer databasen for å matche.
import { table, integer, text, boolean, json, index, sql } from 'sdk/db';
export const users = table('users', {
id: integer('id').primaryKey({ autoIncrement: true }) ,
tgId: integer('tg_id').unique() ,
name: text('name').notNull() ,
lang: text('lang').default('en') ,
isAdmin: boolean('is_admin').default(false) ,
prefs: json('prefs') ,
created: integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`) ,
}, (t) => ({
createdIdx: index('idx_users_created').on(t.created) ,
}));
table(navn, kolonner, ekstra?):
navner SQL-tabellnavnet;kolonnerer en kartlegging av JS-egenskap → kolonnedefinisjon;ekstraer en valgfri callback(t) => ({ … })hvorteksponerer kolonnene (t.createder en referanse til den kolonnen) — deklarer indekser og tabell-nivå-begrensninger her.
Kolonnetyper
| Fabrikk | SQLite-type | Notater |
|---|---|---|
text() |
TEXT | |
integer() |
INTEGER | |
real() |
REAL | alias float() |
numeric() |
NUMERIC | |
blob() |
BLOB | leser/skriver Uint8Array |
boolean() |
INTEGER | lagret som 0/1, lest som true/false |
json() |
TEXT | auto JSON.stringify / JSON.parse |
Kolonnenavn-argumentet er valgfritt — utelat det og JS-nøkkelen brukes.
mode-alternativet styrer hvordan verdier konverteres mellom SQLite og JavaScript.
mode |
Lagret som | JS-verdi |
|---|---|---|
boolean |
INTEGER 0/1 | boolean |
json |
TEXT (JSON) | ethvert objekt/array |
timestamp |
INTEGER (unix sekunder) | Dato |
timestamp_ms |
INTEGER (unix ms) | Dato |
bytes |
BLOB | Uint8Array |
boolean() og json() er snarveier for integer(navn, { mode: 'boolean' }) og text(navn, { mode: 'json' }).
En blob() leser og skriver en Uint8Array — runtime-miljøet har ingen Node Buffer (og en Buffer er en Uint8Array-subklasse, så dette er den bærbare basistypen). mode-en ovenfor styrer kun hvordan en verdi er kodet, uavhengig av kolonnens lagringstype: så blob('col', { mode: 'json' }) er BLOB-motstykket til json() — samme JSON-koding, oppbevart i en BLOB-kolonne i stedet for TEXT.
TLDR:
blob()brukerUint8Array.modestyrer koding, ikke lagring, såblob(..., { mode: 'json' })lagrer JSON som en BLOB, mensjson()lagrer det som TEXT.
Kolonnemodifikatorer
Kolonnemodifikatorer lenkes på en kolonne.
integer('id').primaryKey({ autoIncrement: true })
text('name').notNull()
text('tg').unique()
text('lang').default('en')
integer('created_at', { mode: 'timestamp' }).default(sql`(unixepoch())`)
text('slug').generatedAlwaysAs(sql`lower(name)`, { mode: 'stored' }) // eller 'virtual'
text('email').deprecated('erstattet av login') // merker kolonnen for fjerning
.default()på enjson()-kolonne koder verdien for deg; ensql`…`default sendes videre ordrett..deprecated()er terminal — se Migrasjoner .
Indekser og begrensninger
Indekser og begrensninger deklareres i ekstra-callback-en, der kolonnene er i omfang.
table('t', { /* … */ }, (t) => ({
uq: unique('uq_email').on(t.email) ,
chk: check('chk_done', sql`${t.done} in (0, 1)`) ,
idx: index('idx_name').on(t.col) ,
uidx: uniqueIndex('uidx_email').on(t.email) ,
lower: index('idx_lower').on(sql`lower(${t.email})`) , // uttrykksindeks
active: index('idx_active').on(t.userId).where(sql`done = 0`) , // delvis indeks
}));
Tabell-nivå-modifikatorer lenkes etter table(...): .strict(), .withoutRowid(), .deprecated('grunn').
Ingen fremmednøkler
Runtime-miljøet kjører med PRAGMA foreign_keys av. En deklarert fremmednøkkel ville vært lydløst inaktiv — ingen kaskader, ingen beskyttelse mot foreldreløse poster — noe som er verre enn å ikke ha noen i det hele tatt, så DSL-en gjør det umulig. Spesifikt vil .references() og tabell-nivå foreignKey() kaste feil når de deklareres, og et skjema som bruker dem vil ikke bli distribuert.
Du bør modellere relasjoner med vanlige kolonner (userId: integer('user_id')) og håndheve integritet i applikasjonskoden din: sett inn foreldre før barn, slett barn før foreldre, håndter feil riktig og rydd opp i foreldreløse poster med en LEFT JOIN … WHERE parent.id IS NULL når du trenger det.
Fraværet av fremmednøkler er en bevisst begrensning, ikke en forglemmelse. Ta høyde for det tidlig når du planlegger boten din.
Spørringer
db er en flytende spørringsbygger. Hver spørring er asynkron — terminalmetodene (.all(), .get(), .values(), .run()) returnerer Promises, så bruk alltid await.
import { db } from 'sdk';
import { eq, and, desc, asc, count, sql } from 'sdk/db';
import { users, todos } from '../schema.js';
await db.select().from(todos).all(); // alle rader
await db.select().from(todos).where(eq(todos.id, 1)).get(); // første rad eller null
await db.select().from(todos).values(); // rader som verdi-arrays
await db.select().from(todos)
.where(and(eq(todos.userId, uid) , eq(todos.done, false)))
.orderBy(desc(todos.priority) , asc(todos.id))
.limit(10).offset(20)
.all();
// tilpasset projeksjon: { alias: columnRef | sqlExpr | aggregate }
await db.select({ id: todos.id, title: todos.text, n: count() })
.from(todos).groupBy(todos.userId).having(sql`count(*) > ${1}`).all();
// radtelling — en hjelper, ikke en bygger-terminal:
await db.$count(todos); // alle rader
await db.$count(todos, eq(todos.done, false)); // med filter
- Lenkbar:
.where(),.orderBy(),.limit(),.offset(),.groupBy(),.having(),.distinct(). - Terminaler:
.all(),.get(),.values(). - Tell rader med
db.$count(table, where?)(ellercount()i en projeksjon).
Sett inn, oppdater, slett:
await db.insert(todos).values({ userId: 1, text: 'Kjøp melk' }).run();
await db.insert(todos).values([{ text: 'A' }, { text: 'B' }]).run(); // batch
await db.insert(todos).values({ text: 'X' }).returning().run(); // RETURNING *
await db.insert(users).values({ tgId: 42, name: 'Ann' })
.onConflictDoUpdate({ target: users.tgId, set: { name: 'Ann' } }).run();
await db.update(todos).set({ done: true }).where(eq(todos.id, 1)).run();
await db.delete(todos).where(eq(todos.id, 1)).run();
Merk at en batch-innsetting er én instruksjon, så den er begrenset av SQLite sin variabelgrense (
rader × kolonner); hvis du går over den, feiler innsettingen — del den opp selv.
Operatører importeres fra sdk/db:
import {
eq, ne, gt, gte, lt, lte,
like, notLike,
isNull, isNotNull, and, or, not,
between, notBetween, inArray, notInArray,
count, sum, avg, min, max,
asc, desc,
} from 'sdk/db';
.where(a, b) med flere argumenter er det samme som and(a, b). En sammenlignings andre argument er en verdi som standard, men kan være en annen kolonne eller sql`…` — f.eks. eq(a.x, b.y). Aggregater (count/sum/avg/min/max) er sql-fragmenter for en .select({ … })-projeksjon.
Rå SQL — når byggeren ikke er nok, gå ned til rå SQL. Modus velges av metode: db.run for skriving, db.all for mange rader, db.get for én rad, db.values for rader som posisjonelle arrays.
await db.run('UPDATE todos SET done = 1 WHERE id = :id', { ':id': 5 });
// → { rowsAffected: 3, lastInsertRowid: 0, rows: [] }
await db.all(sql`SELECT * FROM todos WHERE done = ${false}`);
await db.get(sql`SELECT count(*) AS c FROM todos`);
await db.values(sql`SELECT id, text FROM todos`); // → [[1, 'kjøp melk']]
En skriveoperasjon løses til et kjøringsresultat — rowsAffected, lastInsertRowid og rows, som bærer en RETURNING-klausul hvis SQL-en hadde en. Bruk db.values når en spørring projiserer to kolonner med samme navn (en join som SELECT u.id, o.id): db.all nøkler rader etter kolonnenavn, så den andre id-en ville overskrevet den første.
sql`…` -taggen gjør ${value} til en bundet parameter og ${table.column} til en identifikator, og spleiser nøstede sql-fragmenter. Bruk sql.raw('…') for en bokstavelig verdi uten parametere.
Rå spørringer er ikke bundet til en tabell, så radene deres kommer tilbake uten moduskonvertering — en boolsk verdi er
0/1, en JSON-kolonne er en streng, et tidsstempel er et tall. Kun den tabellbundne byggeren konverterer verdier.
Migrasjoner
Databasen din endres etter hvert som boten din vokser. Plattformen holder det trygt ved å skille distribusjon av kode fra endring av data, og ved å klassifisere hver skjemaendring etter hvor risikabel den er.
Distribusjon rører aldri databasen. Når du npx tgcloud push et endret schema.js, registrerer plattformen det nye skjemaet og forteller deg hva databasen ville endret — men bruker ingenting:
npx tgcloud push # distribuer schema.js; rapporterer ventende DB-endringer
npx tgcloud migrate # gå gjennom og bruk dem
npx tgcloud migrate beregner forskjellen mellom skjemaet ditt og den live databasen og guider deg gjennom den. Du blir alltid spurt før noe brukes. Dette betyr at en rutinemessig kodedistribusjon aldri ved et uhell kan utløse en datamigrasjon.
Hver ventende endring bærer en status som bestemmer hvordan migrate behandler den:
| Status | Betydning | I migrate |
|---|---|---|
| safe | Additiv og ikke-blokkerende — en ny tabell, kolonne eller indeks | Brukes sammen i ett steg, ved bekreftelse |
| warning | Potensielt destruktiv eller treg — sletting av noe, eller en indeks på en gigantisk tabell | Presentert én om gangen, hver bekreftet separat |
| manual | Kan ikke gjøres automatisk — f.eks. endring av en kolonnetype | Vist med veiledning; du utfører det for hånd |
| undocumented | Eksisterer i databasen, men ikke i skjemaet ditt | Vist for bevissthet; ikke brukt |
Sikre endringer er raske og reversible i sin natur, så de går gjennom sammen. Hver advarsel er en bevisst, individuell bekreftelse — det er ingen “bruk alt” for destruktive endringer.
Manuelle endringer kommer med en grunn og en foreslått handling. migrate slutter med et sammendrag: hvor mange endringer ble brukt, hoppet over, venter på manuell fiks eller er ikke i skjemaet ditt.
Se
migratefor flaggene (--dry-run,--safe,--yes,--local).
Fjerne ting
Å slette en tabell eller kolonne fra schema.js fjerner den ikke — det ville gjort en utilsiktet sletting katastrofal. For å fjerne noe, marker det som deprecated:
// slett en kolonne
text('email').deprecated('erstattet av login')
// slett en hel tabell
export const oldSessions = table('old_sessions', { /* … */ }).deprecated('ubrukt');
Ved neste migrate dukker det deprecated objektet opp som en warning-status-sletting som du bekrefter individuelt. Når den er borte, fjern deklarasjonen.
Endre kolonnetype
Typeendringer er manuelle — SQLite kan ikke alltid gjøre dem på stedet, og tvangsendring av eksisterende verdier er en vurderingssak. migrate vil vise endringen og begrunnelsen; utfør den selv med rå SQL (db.run(...)), vanligvis ved å opprette en ny kolonne eller tabell, kopiere data og bytte.
SDK-et
Ved kjøretid har en modul ett bibliotek: sdk. Det samler de tre tingene en bot-backend trenger — en database, Telegram Bot API-et og utgående HTTP — med ingenting å installere og ingen legitimasjon å konfigurere. Databasen (db) er dekket i
Databasen
; denne seksjonen dekker api, fetch og den globale console.
import { db, api, fetch, InputFile, BotApiError, EndpointError } from 'sdk'; // hele overflaten
// eller fra undermoduler:
import { table, integer, text, eq, sql } from 'sdk/db';
import { api } from 'sdk/api';
import { fetch } from 'sdk/fetch';
| Import | Hva det er |
|---|---|
db |
Database — spørringsbygger og skjema-DSL → Databasen |
api |
Telegram Bot API — api.sendMessage(...) →
nedenfor
|
fetch |
Utgående HTTP → nedenfor |
InputFile |
En fil (bytes + navn) å laste opp — fungerer med både api og fetch →
Filer
|
EndpointError |
Nekt et Mini App-kall med en melding den kan vise → Endepunkter |
Importer dine egne prosjektmoduler med relativ bane med .js-utvidelsen (fra '../schema.js', fra '../lib/cart.js'). Se
Modulsystemet
.
Bot API-et
api gir deg hele
Telegram Bot API
. Kall hvilken som helst metode som api.<metode>(parametere). Hver nåværende — og fremtidig — Bot API-metode fungerer uten behov for SDK-oppdatering.
import { api } from 'sdk';
const me = await api.getMe(); // → det utpakkede resultatet
await api.sendMessage({ chat_id: id, text: 'Hei!' });
await api.editMessageText({ chat_id, message_id, text: 'Oppdatert' });
await api.answerCallbackQuery({ callback_query_id, text: 'Ferdig' });
Svar-konvolutten pakkes ut. Bot API-et pakker vanligvis resultater inn i { ok: true, result: … }. api returnerer result direkte — getMe() løses til brukerobjektet, ikke en innpakning. Parametere bruker Bot API-ets egne snake_case-navn (chat_id, message_id, reply_markup, …).
Feil kaster BotApiError. Når Bot API-et returnerer { ok: false }, kaster kallet en BotApiError i stedet for å returnere en falsy verdi, slik at du ikke ved et uhell kan ignorere den. Feilen bærer .code (Bot API error_code), .description (den menneskelig lesbare meldingen), .method (hvilken metode som feilet) og .parameters (ekstra data som retry_after ved en 429, eller migrate_to_chat_id). Fang den for å håndtere en forventet feil og kast resten på nytt:
import { api, BotApiError } from 'sdk';
try {
await api.deleteMessage({ chat_id, message_id });
} catch (e) {
if (e instanceof BotApiError && e.code === 400) {
// 400 = meldingen er allerede borte; det er greit her.
} else {
throw e;
}
}
Filer
Jobb med fil-bytes, ikke bare file_ider — last opp nye filer og last ned eksisterende.
Last opp — InputFile. Pakk rå bytes inn i en InputFile (en enkel beholder: bytes, et filnavn og en valgfri MIME type) og send den dit en Bot API-metode forventer en fil:
import { api, InputFile } from 'sdk';
const doc = new InputFile(bytes, 'rapport.pdf', { type: 'application/pdf' });
await api.sendDocument({ chat_id, document: doc });
// filer nøstet inne i parametere fungerer også — f.eks. et album:
await api.sendMediaGroup({ chat_id, media: [
{ type: 'photo', media: new InputFile(a, 'a.jpg', { type: 'image/jpeg' }) },
{ type: 'photo', media: new InputFile(b, 'b.jpg', { type: 'image/jpeg' }) },
]});
Bytes-ene streames til Telegram som en ekte multipart-opplasting — filen buffres ikke i sin helhet til et enkelt kall til plattformen — men Telegrams Bot API-grenser gjelder fortsatt: en bot kan for øyeblikket laste opp opptil 50 MB per fil (og 10 MB for et bilde).
Last ned — med file_id. To hjelpere henter filens bytes tilbake. Plattformen løser file_id-en for deg (den kjører getFile og henter innholdet bak kulissene), så du rører aldri bot-tokenet eller en nedlastings-URL:
const bytes = await api.getFileContent(file_id); // → Uint8Array (hele filen)
const { file, body } = await api.getFileStream(file_id); // stream den i stedet
file.file_unique_id; // getFile-infoen
for await (const chunk of body) { /* Uint8Array */ } // eller buffer den: await stream.bytes()
Telegrams Bot API begrenser
getFile-nedlastinger til 20 MB, så det er taket for begge hjelpere.
HTTP
fetch er en fetch-lignende klient for å kalle omverdenen — tredjeparts API-er, webhooks, alt over HTTP.
import { fetch } from 'sdk';
const res = await fetch('https://api.example.com/users', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Pavel' }) ,
});
if (!res.ok) throw new Error(res.statusText);
const data = await res.json();
Svaret speiler web-plattformen: res.status, res.statusText, res.ok (sant for 200–299), res.url, res.headers (.get(), .has(), .keys(), .entries()) og kropps-lesere await res.json() / await res.text().
Du kan også lese kroppen trinnvis som en stream —
for await (const chunk of res.body) { … }— som er slik du konsumerer server-sent events eller token-for-token-utdata fra AI-API-er.
Kropps-hjelpere setter riktig Content-Type for deg:
await fetch(url, { method: 'POST', body: fetch.body.json({ a: 1 }) }); // application/json
await fetch(url, { method: 'POST', body: fetch.body.form({ a: 1 }) }); // x-www-form-urlencoded
await fetch(url, { method: 'POST', body: fetch.body.text('hi') }); // text/plain
Forespørselskropper kan også bære filer og rå bytes: send en Uint8Array, en InputFile eller en FormData (for multipart/form-data) som body, og bytes-ene streames ut — slik at du kan laste opp til et tredjeparts API på samme måte som du sender filer til Telegram:
import { fetch, FormData, InputFile } from 'sdk';
const form = new FormData();
form.append('file', new InputFile(bytes, 'bilde.jpg', { type: 'image/jpeg' }));
form.append('caption', 'fra boten min');
const res = await fetch('https://api.example.com/upload', { method: 'POST', body: form });
Den oppfører seg ellers som standard fetch du allerede kjenner, med to begrensninger:
- Svaret leses som tekst —
await res.json()/await res.text(), eller streames del for del medres.body. - Total responstak er 30 MB. Den taket dekker hele svaret — streaming med
res.bodylar deg behandle en stor kropp trinnvis, men det hever ikke grensen.
Logging
Den globale standard console er tilgjengelig — ingenting å importere, den er bare der som i enhver JavaScript. Utdataene blir fanget opp og vist av npx tgcloud run, noe som gjør den til ditt primære feilsøkingsverktøy under utvikling.
console.log('behandler', { chatId: id }); // log / debug — vanlig
console.info('startet'); // info — blå
console.warn('rate limited'); // warn — gul
console.error(err); // error — rød, med stack trace
Hver linje er tagget med [fil:linje] den kom fra. console.error og console.trace legger ved en full stack, mens console.warn ikke gjør det. Når du npx tgcloud run en modul, skrives disse linjene ut med et farget prefiks per nivå, tiden siden kjøringen startet og opprinnelsen — se
run
.
Kommandolinjegrensesnitt
tgcloud er broen mellom prosjektmappen din og skyen. Den scaffold-er prosjekter, viser deg hva som endret seg, distribuerer, kjører moduler og bruker databasemigrasjoner. Den trenger
Node.js
18 eller nyere. To måter å få den på:
# Anbefalt — opprett et prosjekt med CLI-en installert i det:
npm create @tgcloud/bot example_bot
# Eller installer CLI-en globalt og initier en tom mappe:
npm install -g @tgcloud/cli
tgcloud init
NPM-pakken er @tgcloud/cli, og kommandoen den installerer er tgcloud. Hver kommando fungerer fra hvilken som helst undermappe i prosjektet ditt.
| Kommando | Formål |
|---|---|
init
|
Opprett et nytt prosjekt i gjeldende mappe |
add
|
Opprett en ny modul (en håndterer, et endepunkt eller lib-modul) |
login
|
Koble prosjektet til en bot (lagrer tokenet) |
status
|
Vis hva som endret seg lokalt vs. skyen |
diff
|
Vis linje-for-linje-endringene |
push
|
Distribuer endrede moduler og Mini App-ens build til skyen |
migrate
|
Bruk skjemaendringer på databasen |
run
|
Kjør en modul på plattformen uten å distribuere |
fetch
|
Sjekk hva som er distribuert, uten å endre lokale filer |
pull
|
Bring lokale filer på linje med skyen |
reset
|
Forkast lokale endringer; gjenopprett fra skyens tilstand |
webhook
|
Inspiser og synkroniser den plattform-administrerte webhook-en |
completion
|
Skriv ut et shell-fullføringsskript (bash/zsh/fish) |
upgrade
|
Flytt et pre-0.2.0-prosjektoppsett inn i tgcloud/ |
Autentisering
Et prosjekt er knyttet til én bot via tokenet, som har formatet app<id>:<secret>. app<id>-delen er offentlig og kan skrives ut; hemmeligheten vises aldri i logger eller feil.
Tokenet løses i denne rekkefølgen:
TGCLOUD_TOKENmiljøvariabel — for CI; skrives aldri til disk..tgcloud/credentials— skrevet avnpx tgcloud login.- Ingen av dem → en feil som peker deg til
npx tgcloud login.
CLI-en ber aldri om et token midt i en kommando. Innlogging er alltid det eksplisitte login-steget. Hvis et lagret token blir ugyldig, fjerner CLI-en det og ber deg login på nytt.
init
npx tgcloud init
Oppretter et nytt prosjekt i gjeldende mappe: tgcloud/-mappen med schema.js, lib/, handlers/ og en starthåndterer, pluss tgcloud.jsonc, AGENTS.md, docs/ og .tgcloud/-tilstandsmappen. Kjør den i roten av et eksisterende front-end-prosjekt også — den legger til tgcloud/-mappen og dokumentene, og overskriver aldri filene dine. Hvis den gjenkjenner front-enden, peker den også tgcloud.jsonc mot build-mappen og legger til et deploy-skript (bygg, deretter push) i package.json — kun hvis du ikke har et deploy-skript fra før. Se
Mini App front-end
.
init nekter å opprette et prosjekt inne i et annet prosjekt. Å kjøre den på nytt i et prosjekts egen rot er greit og legger bare til det som mangler.
add
npx tgcloud add <mål>
Oppretter en enkelt ny modul, koblet opp og klar til redigering — en håndterer, et endepunkt eller en lib/-modul.
npx tgcloud add handlers/callback_query # en ny oppdateringshåndterer
npx tgcloud add endpoints/getProfile # et nytt endepunkt for Mini App-en
npx tgcloud add lib/cart # en ny delt modul
Merk at
addaldri overskriver en eksisterende fil.
<mål> er modulens bane (en avsluttende .js er valgfri). For handlers/ må navnet være en Telegram-oppdateringstype. Plattformen annonserer det gyldige settet, så et ugyldig navn avvises med en gang. handlers/ og endpoints/ er flate, mens lib/ kan være nøstet (lib/payments/stripe).
Modulnavnet er påkrevd. Å gi kun mappen er en feil — men en nyttig en: for handlers/ lister den opp oppdateringstypene du ikke allerede har, så du kan kopiere en.
$ npx tgcloud add handlers
Feil: Spesifiser et navn, f.eks. "handlers/callback_query". Tilgjengelige handlers/-typer: callback_query, inline_query, chat_member, …
<Tab>-fullføring tilbyr det samme settet — secompletion.
Den genererte filen har en live export default, så håndtereren er aktiv så snart du distribuerer — det er ingenting å avkommentere. Distribuer den nye modulen med
push
.
login
npx tgcloud login
Ber om CLI-tilgangstokenet ditt — fra
@BotFather
→ boten din → Serverless → CLI Access → Access token, et eget token fra
botens API-token
— validerer det mot plattformen og lagrer det i .tgcloud/credentials.
loginer den eneste kommandoen som ber om et token, og den krever en interaktiv terminal. I CI, settTGCLOUD_TOKENi stedet.
status
npx tgcloud status
Viser, per fil, hva som har endret seg mellom arbeidsmappen din og den distribuerte kopien: modifisert, ny, slettet, uendret. Den fungerer offline, og sammenligner mot tilstanden i skyen fra din siste push, pull eller fetch. Den advarer også om løse .js-filer inne i tgcloud/ som ikke vil bli distribuert.
For et prosjekt med en Mini App viser status også hva som endret seg i build-mappen dens siden siste distribusjon.
diff
npx tgcloud diff
Som status, men viser de faktiske linje-for-linje-forskjellene for endrede moduler. Også offline.
push
npx tgcloud push [filer...]
Distribuerer prosjektet ditt til skyen i én atomisk batch.
Med ingen argumenter distribuerer den hele prosjektet, og den distribuerte tilstanden gjøres for å speile mappen din nøyaktig — moduler du slettet lokalt fjernes i skyen.
Med fil- eller mappeargumenter (npx tgcloud push tgcloud/handlers/message.js, npx tgcloud push handlers/), distribuerer den kun endringene i de filene. En målrettet push sletter aldri andre moduler. Et mål kan staves som filbanen (tgcloud/handlers/message.js), banen inne i tgcloud/ (handlers/message.js) eller modulnavnet (handlers/message) — de betyr alle den samme modulen.
Alternativet er --force — for å hoppe over samtidighets-sjekken og overskrive hva enn som er i skyen. Bruk den kun når du er sikker (se
Holde seg synkronisert
).
Hvis tgcloud.jsonc navngir en statisk mappe, distribuerer en full push den i samme batch — kun filene som endret seg lastes opp — og skriver ut nettstedets adresse. Bygg først, fordi push aldri kjører bygget ditt (npm run deploy gjør begge deler). npx tgcloud push dist/ distribuerer kun de statiske filene, og en push som kun navngir moduler lar dem være uberørt.
Etter en distribusjon, hvis schema.js endret seg og databasen er ute av synk, skriver push ut et sammendrag av de ventende endringene og foreslår npx tgcloud migrate. Den bruker aldri dem selv.
migrate
npx tgcloud migrate
Bruker skjemaendringene dine på databasen. Den beregner forskjellen mellom schema.js og den live databasen, deretter guider den deg gjennom den ett steg av gangen med en løpende [N/M]-teller:
- Et kort sammendrag av alle ventende endringer.
- Sikre endringer, brukt sammen i ett enkelt steg ved din bekreftelse.
- Advarsler (slettinger, trege operasjoner), én om gangen, hver bekreftet separat.
- Manuelle endringer, vist med en grunn og foreslått handling, ikke brukt automatisk.
- Udokumenterte objekter (i databasen, men ikke i skjemaet ditt), vist for bevissthet.
Den slutter med et sammendrag: brukt, hoppet over, venter på manuell fiks, ikke i skjemaet.
Se Migrasjoner for modellen.
Alternativer: --dry-run (skriv ut alt, bruk ingenting), --safe (bruk sikre endringer automatisk, hopp over advarsler), --yes (bruk sikre endringer og hver advarsel automatisk, hopp over manuell — bruk med forsiktighet), --local (diff mot din lokale schema.js i stedet for den distribuerte). Uten flagg krever migrate en terminal og gir feil i et ikke-interaktivt miljø i stedet for å gjette.
run
npx tgcloud run <modul> [args] [--ctx <json5>]
Utfører en håndterer eller et endepunkt på plattformen uten å distribuere, ved bruk av dine nåværende lokale filer. Dette er den raske indre loopen for å teste logikk.
<modul>— et bart navn (søkes underhandlers/ogendpoints/) eller en bane somhandlers/message. Et bart navn som eksisterer i begge mapper må staves med mappen sin.[args]— nyttelasten sendt til håndtereren, skrevet i JSON5 slik at du kan hoppe over å sitere nøkler. Det er oppdateringstype-objektet håndtereren din mottar (f.eks. en melding forhandlers/message), ellerinput-en til et endepunkt, og det må være et objekt — ikke en array eller en bar streng.--ctx <json5>— modulens kontekstobjekt (dets andre argument), også JSON5. Bruk den for å sende inn det koden din leser avctx— den rå oppdateringen for en håndterer,--ctx '{ update: { update_id: 1 } }', eller init-data for et endepunkt,--ctx '{ initData: { user: { id: 1 } } }'.runverifiserer den ikke, siden du tester logikken din, ikke plattformens sjekker.
npx tgcloud run handlers/message '{ chat: { id: 1 }, text: "hi" }'
Plattformen kjører modulen mot modulområdet satt sammen fra ditt lokale prosjekt (så lokalt endret lib/-kode brukes også) og returnerer returverdien, alt logget med console.*, og tiden som har gått. Les store argumenter fra en fil med npx tgcloud run handlers/message "$(cat message.json5)".
fetch
npx tgcloud fetch
Sjekker hva som for øyeblikket er distribuert, uten å røre arbeidsfilene dine. Etterpå sammenligner status og diff mot den nyeste skytilstanden. Nyttig for å se på en konflikt før du bestemmer deg for hvordan du skal løse den.
pull
npx tgcloud pull
Bring det lokale prosjektet ditt på linje med skyen ved å oppdatere arbeidsfilene dine til den distribuerte tilstanden.
pull skriver aldri Mini App-ens build-mappe.
reset
npx tgcloud reset
Forkaster lokale endringer og gjenoppretter modulene fra den sist kjente skytilstanden. Bruk den for å kaste bort et eksperiment. Mini App-ens build-mappe blir ikke rørt.
webhook
npx tgcloud webhook
npx tgcloud webhook sync [--drop-pending]
Telegram leverer oppdateringer til boten din gjennom en webhook, som plattformen administrerer for deg — du peker den aldri noe sted for hånd. npx tgcloud webhook viser gjeldende tilstand: URL-en, allowed_updates-listen, hvor mange oppdateringer som venter, den siste leveringsfeilen (hvis noen), og om den er i synk med de distribuerte håndtererene dine.
“I synk” betyr at webhook-en peker på plattformen og at allowed_updates samsvarer med håndtererene du har distribuert — slik at Telegram leverer nøyaktig oppdateringstypene du håndterer, og ingenting annet. Distribusjon av en ny håndterer (eller fjerning av en) kan etterlate webhook-en ute av synk til den er oppdatert; npx tgcloud status flagger dette også.
npx tgcloud webhook sync fikser det — den peker webhook-en på nytt mot plattformen og bygger allowed_updates på nytt fra dine distribuerte håndterere. Legg til --drop-pending for å forkaste oppdateringer Telegram allerede hadde satt i kø før synkroniseringen (ellers leveres de så snart webhook-en er sunn igjen).
completion
Merk: tab-fullføring fungerer kun når en bar
tgclouder iPATH-en din — så installer den globalt (npm install -g @tgcloud/cli) eller plasser binærfilen iPATH. Den kan ikke koble tilnpx.
tgcloud completion <bash|zsh|fish>
Skriver ut et shell-fullføringsskript til stdout. Aktiver det én gang, så fullfører <Tab> kommandoer, flagg, moduler, handler-oppdateringstyper du ikke har ennå, og dine lokale kjørbare moduler.
# bash — trenger bash-completion-pakken:
echo 'eval "$(tgcloud completion bash)"' >> ~/.bashrc
# zsh — sørg for at `autoload -U compinit && compinit` kjører i din ~/.zshrc:
echo 'eval "$(tgcloud completion zsh)"' >> ~/.zshrc
# fish:
tgcloud completion fish > ~/.config/fish/completions/tgcloud.fish
Start skallet på nytt (eller re-source filen) etterpå. Å kjøre tgcloud completion uten skall skriver ut disse instruksjonene igjen.
upgrade
npx tgcloud upgrade [--dry-run]
Engangsmigrasjon for prosjekter opprettet før CLI 0.2.0, som beholdt schema.js, lib/ og handlers/ i prosjektroten. Den flytter dem inn i tgcloud/. Koblingen din til boten beholdes, og ingenting trenger å distribueres på nytt. --dry-run skriver ut de planlagte flyttingene uten å røre noe. Andre kommandoer kjører ikke på det gamle oppsettet før du oppgraderer.
Holde seg synkronisert
Hvert prosjekt har en monotont økende revisjon i skyen, oppdatert ved hver distribusjon. CLI-en husker revisjonen den sist synkroniserte med og sender den ved hver push. Hvis skyen har beveget seg fremover — fordi en annen maskin eller lagkamerat distribuerte — avvises push-en i stedet for at arbeidet deres lydløst overskrives, og CLI-en tilbyr tre måter videre:
npx tgcloud fetch # se hva som endret seg i skyen, sjekk deretter på nytt
npx tgcloud pull # hent det nyeste inn i arbeidsfilene dine
npx tgcloud push --force # overskriv skytilstanden (farlig)
Denne optimistiske samtidighets-sjekken er grunnen til at du kan dele en bot på tvers av et team uten en låst distribusjonsprosess. Kommandoer avsluttes med ikke-null ved feil — en avvist distribusjon, en feilet migrasjon, en autentiseringsfeil, en modul som kastet feil under run — så de kan kombineres rent i skript og CI-pipelines.