ChatGPT: back end com NodeJS
Desenvolva um back end com NodeJS e express que recebe um prompt, repassa-o ao ChatGPT usando a biblioteca da OpenAI e devolve a resposta ao cliente, com a chave de API guardada no .env.
1. Visão geral
Quando utilizamos o ChatGPT, aquilo que enviamos a ele recebe o nome de prompt. Em função do nosso prompt, o ChatGPT produz uma resposta, chamada completion. Veja.

O prompt vai do usuário ao ChatGPT; a completion volta como resposta.
Neste material, vamos desenvolver uma aplicação Back End com NodeJS que se comunica com o ChatGPT. Ela oferece um único endpoint:
POST /pergunte-ao-chatgpt
No corpo da requisição, enviamos um objeto JSON no seguinte formato.
{"prompt": "Por que o céu é azul?"}
A aplicação repassa o prompt recebido ao ChatGPT. Ela inclui, também, a chave de acesso à API. Por sua vez, o ChatGPT produz uma resposta (completion) e a entrega ao Back End. O Back End, então, entrega a resposta produzida pelo ChatGPT à aplicação cliente. No material, para simular as requisições, vamos usar a Thunder Client, extensão do VS Code que opera como um cliente HTTP. Veja.

Arquitetura da aplicação: cliente, back end, arquivo .env e ChatGPT.
O que você vai aprender
- Como criar um projeto NodeJS com npm e executá-lo com o nodemon
- Como guardar a chave de API em um arquivo
.envcom o pacote dotenv - Como receber requisições com o express e testá-las com a Thunder Client
- Como usar a biblioteca da OpenAI para enviar um prompt ao ChatGPT
- O que são modelos, papéis (roles) e tokens, e como tratar a resposta
O que você vai precisar
- NodeJS e npm instalados
- VS Code com a extensão Thunder Client
- Uma chave de API da OpenAI (veja o codelab "ChatGPT: criando uma conta e obtendo a chave de API")
2. Novo projeto e nodemon
Crie uma pasta para abrigar os arquivos deste novo projeto. No Windows, uma sugestão é
C:\Users\usuario\Documents\dev\chatgpt_backend
Em sistemas Unix like, use
/home/usuario/dev/chatgpt_backend
A seguir, abra o VS Code e clique em File >> Open Folder para mantê-lo vinculado à nova pasta. Por fim, ainda no VS Code, clique em Terminal >> New Terminal para abrir um novo terminal interno do VS Code, o que vai simplificar diversas tarefas. No terminal, execute
Terminal
npm init -y
para criar um projeto gerenciado pelo npm. Na prática, este comando apenas cria um arquivo chamado package.json. Ele é utilizado na definição de dependências do projeto, scripts de execução etc.
Crie também um arquivo index.js. Ele será o arquivo principal da aplicação.

O projeto com os arquivos index.js e package.json.
Ainda no VS Code, clique em File >> Auto Save para que as suas edições sejam salvas automaticamente.
Conforme editamos o código, desejamos realizar novos testes considerando as novas atualizações. É pouco prático reiniciar o servidor manualmente a cada edição. Por isso, vamos utilizar o pacote nodemon (de node monitor). Ele monitora os arquivos envolvidos na execução do projeto e, quando um deles é atualizado, o nodemon reinicia a instância do NodeJS que representa o nosso servidor. Faça a sua instalação com
Terminal
npm install nodemon --save-dev
Para utilizar o nodemon, abra o package.json e crie um script chamado start:dev, como a seguir.
package.json
{
"name": "chatgpt_js_apostila",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start:dev": "nodemon index.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"nodemon": "^3.0.1"
}
}
No terminal, use
Terminal
npm run start:dev
para colocar o projeto em funcionamento. Veja o resultado esperado.

O nodemon executando o index.js e aguardando alterações.
3. Armazenando a chave de acesso: dotenv
Não podemos deixar a chave de acesso à API fixa no código por, pelo menos, duas razões.
- Controle de versão em repositórios públicos: se utilizarmos um repositório público para o controle de versão, a chave ficará visível para todos na Internet.
- Valor variável em função do ambiente: em ambiente de desenvolvimento, podemos utilizar uma chave reservada apenas para teste. Em ambiente de produção, é provável que a chave seja outra, reservada apenas para produção. Assim, a chave é um valor que varia em função do ambiente. Ela é uma variável de ambiente. Queremos um jeito simples de trocar esse valor sem ter de ficar editando o código explicitamente sempre que fizermos uma nova implantação.
Para resolver esse problema, utilizaremos uma biblioteca chamada dotenv. Os passos são os seguintes.
- Definimos um arquivo chamado
.envna raiz do projeto. Ele abriga as variáveis de ambiente no formatoCHAVE=VALOR. - Instalamos o pacote dotenv usando o npm.
- Importamos o pacote dotenv e chamamos seu método
config. Ele é responsável por ler o conteúdo do arquivo.enve disponibilizar as variáveis ali definidas como variáveis de ambiente. - No código Javascript, utilizamos o objeto global do Node (
process) para acessar a variável de interesse, da seguinte forma:process.env.CHAVE.
Instale o módulo dotenv com
Terminal
npm install dotenv
Crie um arquivo .env na raiz do seu projeto.

O arquivo .env na raiz do projeto.
Veja o seu conteúdo.
.env
OPENAI_API_KEY=sua chave aqui, sem espaços em branco ou aspas
No arquivo index.js, importe o pacote dotenv, chame a sua função config e exiba a chave para verificar se tudo deu certo.
index.js
require('dotenv').config()
const OPENAI_API_KEY = process.env.OPENAI_API_KEY
console.log(OPENAI_API_KEY)
Veja o resultado esperado.

A chave lida do .env aparece no terminal.
.gitignore
Há dois conteúdos que devem ser excluídos do controle de versão:
- pasta
node_modules(não se faz controle de versão de bibliotecas, pacotes, módulos, código compilado etc.) - arquivo
.env(ele contém a chave!)
Por isso, crie um arquivo chamado .gitignore na raiz da aplicação. Veja seu conteúdo.
.gitignore
.env
node_modules
4. Recebendo requisições com o express
A fim de especificar nosso endpoint e receber requisições HTTP, vamos utilizar o pacote express. Faça a sua instalação com
Terminal
npm install express
No arquivo index.js, os passos são os seguintes:
- importar o express
- construir um objeto (que chamaremos de
app) que viabiliza a definição de endpoints - aplicar um middleware de transformação JSON
- definir o endpoint
- colocar o servidor em execução
Veja.
index.js
require('dotenv').config()
//importamos o express
const express = require('express')
//construímos o objeto que viabiliza a especificação de endpoints
const app = express()
//aplicamos o middleware de transformação JSON
app.use(express.json())
const OPENAI_API_KEY = process.env.OPENAI_API_KEY
//especificamos o endpoint de interesse
//POST /pergunte-ao-chatgpt
app.post('/pergunte-ao-chatgpt', (req, res) => {
//respondemos um 'ok' só para testar
res.send('ok')
})
//colocamos o servidor em execução na porta 4000
const PORT = 4000
app.listen(PORT, () => console.log(`Em execução na porta ${PORT}`))
Veja o resultado esperado.

O servidor em execução na porta 4000.
Testando o endpoint com a Thunder Client
A Thunder Client é uma extensão do VS Code que opera como um cliente HTTP. Caso ainda não possua, faça a sua instalação.

Instalando a extensão Thunder Client.
Depois de fazer a instalação, você pode criar uma requisição para testar. Observe.

Uma requisição POST para localhost:4000/pergunte-ao-chatgpt devolve "ok".
5. Configurando o cliente da OpenAI
A fim de conversar com o ChatGPT, precisamos instalar o pacote da openai. Faça isso com
Terminal
npm install openai
A seguir
- importamos a classe
OpenAI - utilizamos seu construtor para obter um objeto capaz de conversar com o ChatGPT. Ao construtor, entregamos a chave de API.
Veja.
index.js
require('dotenv').config()
const { OpenAI } = require ('openai')
//importamos o express
const express = require('express')
//construímos o objeto que viabiliza a especificação de endpoints
const app = express()
//aplicamos o middleware de transformação JSON
app.use(express.json())
const OPENAI_API_KEY = process.env.OPENAI_API_KEY
const openai = new OpenAI(OPENAI_API_KEY)
//especificamos o endpoint de interesse
//POST /pergunte-ao-chatgpt
app.post('/pergunte-ao-chatgpt', (req, res) => {
//respondemos um 'ok' só para testar
res.send('ok')
})
//colocamos o servidor em execução na porta 4000
const PORT = 4000
app.listen(PORT, () => console.log(`Em execução na porta ${PORT}`))
6. Extraindo o prompt da requisição
O primeiro passo da implementação do endpoint consiste em extrair o prompt que o cliente (Thunder Client) enviou ao Back End. O cliente deverá enviar um objeto JSON com uma única propriedade chamada prompt. Assim:
{"prompt": "Por que o céu é azul?"}
Começamos extraindo o prompt e apenas devolvendo-o ao cliente, a fim de verificar se está tudo certo.
index.js
// ...
app.post('/pergunte-ao-chatgpt', (req, res) => {
//desestruturamos o corpo da requisição, pegando apenas o prompt
const { prompt } = req.body
console.log(prompt)
//apenas devolvemos o prompt ao cliente, realizando um teste breve
res.json({seuPrompt: prompt})
})
// ...
Faça um teste na Thunder Client.

O endpoint devolve o prompt recebido.
7. Conversando com o ChatGPT
A seguir, vamos utilizar o cliente da OpenAI para conversar com o ChatGPT. Os passos serão os seguintes.
Escolha do modelo
Há modelos aprimorados para conversação, para a edição de imagens, para o tratamento de áudio etc. Veja a lista de modelos que se encontravam disponíveis no momento em que este documento foi escrito.

Modelos disponíveis em outubro de 2023.
Essa é a lista de modelos disponíveis existente no momento em que esse documento foi construído. É recomendável visitar o link oficial para conhecer os mais recentes: https://platform.openai.com/docs/models/overview.
Escolha do "papel" (role)
O papel pode ser user, assistant ou system.
| user | assistant | system |
|---|---|---|
| É o mais comumente utilizado. Representa o usuário que está conversando com o ChatGPT. | É a "criatura" que interage com o usuário conforme ele envia mensagens. Não é muito utilizado. Pode ser interessante caso desejemos simular uma conversa prévia com o ChatGPT, associando mensagens ao assistant, o que o fará "pensar" que foi ele quem gerou aquelas respostas. | Serve para fornecer instruções ou configurar comportamentos que não são parte da conversa entre o user e o assistant. Pode ser usado para influenciar a conversa, configurando, por exemplo, um contexto em que a conversa deve acontecer, sem que as instruções sejam parte da conversa. |
Número máximo de tokens
Um token é uma sequência de caracteres. Veja o que a documentação fala sobre eles.
"A helpful rule of thumb is that one token generally corresponds to ~4 characters of text for common English text. This translates to roughly ¾ of a word (so 100 tokens ~= 75 words)."
Esse valor é importante pois o pagamento é feito por tokens. Quando limitamos o número máximo de tokens, estamos cuidando para que cada requisição não tenha custo muito alto.
Veja o código. Note que a função do endpoint passa a ser async, pois aguardamos (await) a resposta do ChatGPT.
index.js
// ...
app.post('/pergunte-ao-chatgpt', async (req, res) => {
const { prompt } = req.body
//escolha dos parâmetros
const model = 'gpt-3.5-turbo'
const role = 'user'
const max_tokens = 50
//comunicação com o ChatGPT
const completion = await openai.chat.completions.create({
messages: [{ role: role, content: prompt}],
model: model,
max_tokens: max_tokens
});
res.json({seuPrompt: prompt})
})
// ...
8. Tratando a resposta
A seguir, precisamos tratar a resposta. Veja a estrutura que ela possui, segundo a documentação.
Resposta (exemplo da documentação)
{
"id": "chatcmpl-123",
"object": "chat.completion",
"created": 1677652288,
"model": "gpt-3.5-turbo-0613",
"choices": [{
"index": 0,
"message": {
"role": "assistant",
"content": "\n\nHello there, how may I assist you today?"
},
"finish_reason": "stop"
}],
"usage": {
"prompt_tokens": 9,
"completion_tokens": 12,
"total_tokens": 21
}
}
Trata-se de um objeto JSON que possui uma chave choices associada a uma coleção JSON. A coleção possui objetos compostos por, entre outras coisas, uma chave chamada message. Por sua vez, ela está associada a um objeto que possui uma chave content. O valor associado a ela é o que nos interessa. Veja como podemos devolvê-la ao cliente. Há apenas uma "choice". Portanto, acessamos a coleção choices na posição 0.
index.js
// ...
app.post('/pergunte-ao-chatgpt', async (req, res) => {
const { prompt } = req.body
//escolha dos parâmetros
const model = 'gpt-3.5-turbo'
const role = 'user'
const max_tokens = 50
//comunicação com o ChatGPT
const completion = await openai.chat.completions.create({
messages: [{ role: role, content: prompt}],
model: model,
max_tokens: max_tokens
});
res.json({completion: completion.choices[0].message.content})
})
// ...
Faça novo teste com a Thunder Client.

A resposta do ChatGPT chega ao cliente.
9. Encerramento
Parabéns! Você construiu um back end com NodeJS e express que recebe um prompt, conversa com o ChatGPT por meio da biblioteca da OpenAI e devolve a completion ao cliente, mantendo a chave de API fora do código.
Referências
- OpenAI. OpenAI, 2023. Disponível em https://openai.com/. Acesso em outubro de 2023.
- Modelos: https://platform.openai.com/docs/models/overview
- Chat completions: https://platform.openai.com/docs/api-reference/chat/create