Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/zingarelli/code-connect
Uma rede social para devs. Desenvolvido em Next.js
https://github.com/zingarelli/code-connect
aprendi-na-alura nextjs14 prisma vitrinedev
Last synced: 19 days ago
JSON representation
Uma rede social para devs. Desenvolvido em Next.js
- Host: GitHub
- URL: https://github.com/zingarelli/code-connect
- Owner: zingarelli
- Created: 2024-07-16T18:50:43.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2024-09-11T19:56:26.000Z (4 months ago)
- Last Synced: 2024-09-12T06:04:06.462Z (4 months ago)
- Topics: aprendi-na-alura, nextjs14, prisma, vitrinedev
- Language: JavaScript
- Homepage: https://code-connect-eosin.vercel.app
- Size: 861 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Code Connect
Uma rede social para Devs. Projeto desenvolvido em Next.js versão 14. Uma lista de posts é exibida, sendo possível clicar em um post para ver o conteúdo completo. Há também um mecanismo de busca por título de um post. Além disso, é possível "curtir" um post e adicionar comentários.
| :placard: Vitrine.Dev | |
| ------------- | --- |
| :sparkles: Nome | **Code Connect**
| :label: Tecnologias | Next, Prisma
| :rocket: URL | https://code-connect-eosin.vercel.app
| :fire: Curso | https://www.alura.com.br/formacao-next-js-14-aplicacoes-robustas-alta-produtividade![](https://github.com/user-attachments/assets/e1d1e74d-119b-4614-a8d7-0e9272a8ace8#vitrinedev)
## Créditos
Este projeto é resultado dos cursos da Alura para a formação [Next.js 14: desenvolvendo aplicações robustas com alta produtividade](https://www.alura.com.br/formacao-next-js-14-aplicacoes-robustas-alta-produtividade), ministrados pela [Patrícia Silva](https://github.com/gss-patricia) e pelo [Vinicios Neves](https://www.linkedin.com/in/vinny-neves/).
## Detalhes do projeto
Este é meu primeiro projeto FullStack trabalhando com Next. Ele é o resultado dos cursos introdutórios em que eu aprendi Next e tecnologias associadas.
Atualmente, o Code Connect consiste em **duas telas: Home e Post**.
A tela Home é a página inicial, que exibe todos os posts cadastrados na base de dados, utilizando de paginação para exibir somente 4 posts por página, como pode ser visto na captura de tela abaixo:
![captura de tela da página inicial do Code Connect exibindo uma barra lateral com a logo, uma barra de pesquisa no topo e uma seção exibindo quatro posts, cada um com uma imagem, título, descrição, link para ver mais detalhes e o nome da autora. No final da página há um link para exibir os próximos posts](https://github.com/user-attachments/assets/e7871720-5d43-4902-af04-2eea89b0522b)
Quando a pessoal clica em um post, é redirecionada para a tela de Post, exibe o conteúdo da postagem, incluindo uma imagem, título e conteúdo do post. Há também uma seção para exibir código em formato markdown - isso é feito com a biblioteca remark e você pode ver mais detalhes sobre ela [nessa Seção](#exibição-de-markdown-usando-remark). Segue uma captura de tela de exibição de um post:
![captura de tela da postagem cujo título é "Otimização de Performance no React"](https://github.com/user-attachments/assets/2538ec61-e44e-40ff-a91c-dafd27e30970)
Além das duas telas, há uma **funcionalidade de busca por post**, por meio de uma barra de pesquisa que fica no topo das telas. Ao digitar algum termo, a tela exibe posts que tenham o conteúdo da busca no título.
### Evoluções tecnológicas
O projeto está **em evolução**, com novas tecnologias e soluções sendo adicionadas a cada atualização. Para isso, cada evolução se encontra em branches separadas.
A **branch main** contém a versão inicial do projeto, que utiliza um Back End mockado pelo JSON Server.
A **branch postgres_prisma** fornece uma solução FullStack completa, com Front e Back End. Nela, criamos um contêiner no Docker para subir um banco Postgres, e utilizamos o Prisma para popular o banco e fazer as consultas. Por fim, é feito o deploy na Vercel, e o projeto pode ser visto [neste link](https://code-connect-eosin.vercel.app). Nessa branch também estão inclusas as mecânicas para curtir um post e também adicionar comentários e respostas a comentários.
Informações sobre cada tecnologia utilizada podem ser vistas na [Seção Detalhes Técnicos](#detalhes-técnicos).
## Instalação
O projeto foi desenvolvido em [Next.js](https://nextjs.org/) utlizando o [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
Após cloná-lo ou baixá-lo, abra um terminal, navegue até a pasta do projeto e rode o comando abaixo para instalar as dependências necessárias.
npm install
Feito isso, o app pode ser iniciado em modo de desenvolvimento com o seguinte comando:
npm run dev
O app irá rodar na URL [http://localhost:3000](http://localhost:3000).
Detalhes sobre como fazer o deploy na [Vercel](https://vercel.com/) podem ser vistos [nesta Seção](#deploy-fullstack-na-vercel).
## Detalhes Técnicos
### Versão inicial com JSON Server
> A versão inicial que se encontra na **branch main**.
A primeira versão do projeto utiliza um back end mockado por meio do [JSON Server](https://www.npmjs.com/package/json-server).
Para poder rodá-lo localmente, crie uma pasta fora da pasta da aplicação e salve nela [este arquivo JSON](https://raw.githubusercontent.com/viniciosneves/code-connect-assets/main/posts.json). O arquivo servirá como o banco de dados de postagens a serem exibidas na aplicação.
Na linha de comando, navegue até a pasta criada e faça a instalação local do JSON Server na versão específica:
npm i [email protected]
O comando abaixo irá rodar o servidor com o arquivo baixado e na porta 3042 (você pode escolher a porta que quiser):
npx json-server posts.json -p 3042
Você pode consultar a resposta da API na URL: [http://localhost:3042/posts](http://localhost:3042/posts). Ela irá retornar um array de objetos.
#### Trabalhando com paginação
O JSON Server oferece uma solução de paginação utilizando dois parâmetros (a query string da URL): `_page` e `_per_page`. Exemplo:
http://localhost:3042/posts?_page=2&_per_page=2
Quando se trabalha com paginação, a resposta da API é diferente, trazendo um objeto ao invés de um array de objetos post.
```json
{
"first": 1,
"prev": 1,
"next": 2,
"last": 6,
"pages": 6,
"items": 12,
"data": [{}]
}
```Agora os dados de post estão na propriedade `data`, com os items baseados no que foi passado em `page` e `per_page`.
As propriedades `prev` e `next` podem nos ajudar a navegar pelas páginas e obter os dados correspondentes.
Exemplo de código utilizando paginação para exibir os dados:
```jsx
export default async function Home({ searchParams }) {
const currentPage = searchParams?.page || 1;
const { data: posts, prev, next } = await getAllPosts(currentPage);
return (
{posts.map(post => )}
{prev && Página anterior}
{next && Próxima página}
);
}
```A prop **`searchParams`** é fornecida pelo Next para acessarmos os parâmetros contidos na query string da URL da página. O acesso é feito como se fosse um objeto.
### Monitoramento de logs usando o winston
O "winston" é uma biblioteca especializada em criar diferentes tipos de logs para uma aplicação.
O [repositório do projeto](https://github.com/winstonjs/winston) no GitHub possui exemplos e a documentação.
Criando um logger:
```js
import { createLogger, format, transports } from 'winston';
const logger = createLogger({
level: 'info',
format: format.json(),
transports: [
//
// - Write all logs with importance level of `error` or less to `error.log`
// - Write all logs with importance level of `info` or less to `combined.log`
//
new transports.File({ filename: 'error.log', level: 'error' }),
new transports.File({ filename: 'combined.log' }),
],
});
export default logger;
```O winston trabalha com níveis de log: error, warn, info, http, verbose, debug, silly. O nível "error" é o mais severo e importante (valor 0) e o "silly" é o menos importante (valor 6). Quando informado o nível no `createLogger` (`level`), ele só irá criar logs daquele nível para baixo.
A propriedade `transports` são os arquivos que serão usados para registrar os logs. Quando informado o `level`, o transport correspondente irá registrar somente os logs daquele nível para baixo.
Você **precisa criar os arquivos** em que os logs serão gravados. Eles devem ser criados na raiz do projeto com o nome que você definiu para cada um no código.
Exemplo de uso:
```jsx
import logger from "@/logger";const getAllPosts = async () => {
const resp = await fetch('http://localhost:3042/posts');
if (!resp.ok) {
// using winston for logging
logger.error('Função getAllPosts --> erro ao obter as postagens da API');
return [];
}
logger.info('Função getAllPosts --> posts obtidos com sucesso');
return resp.json();
}
```### Exibição de markdown usando remark
O Code Connect exibe postagens de tecnologia e, dentre o conteúdo em cada postagem, há uma seção que exibe códigos. Esses códigos são escritos em formato markdown.
O [`remark`](https://github.com/remarkjs/remark) é uma biblioteca sugerida pelo Next para renderizar conteúdo markdown. Ele possui um plugin `remark-html` para conversão do conteúdo markdown para HTML.
Instalação:
npm i remark remark-html
Exemplo de uso convertendo um conteúdo markdown para HTML:
```jsx
// -- app/posts/[slug]/page.js
import { remark } from "remark";
import html from "remark-html";const markdownToHtml = async (data) => {
const processedContent = await remark()
.use(html) // html plugin for remark
.process(data) // markdown data
return processedContent.toString();
}const getPostBySlug = async (slug) => {
// code omitted
// assume data is an array of objects
// retrieved from the API
const post = data[0];
post.markdown = await markdownToHtml(post.markdown);
return post;
}const PagePost = async ({ params }) => {
const post = await getPostBySlug(params.slug);return (
);
}
export default PagePost;
```### Versão FullStack
> A versão FullStack se encontra na **branch postgres_prisma**.
A segunda versão do projeto utiliza o [Docker Compose](https://docs.docker.com/compose/) para subir um banco de dados Postgres (versão 15). Para interação com este banco por meio do Next, utilizamos o ORM [Prisma](https://www.prisma.io/).
#### Docker Compose
A configuração para subir o contêiner com o serviço do Postgres está no arquivo `docker-compose.yaml`. Utilizamos o comando `docker compose up -d` para baixar os arquivos necessários e criar o contêiner.
No caso de reiniciar o contêiner (por exemplo, se a máquina foi reiniciada e o Docker Desktop também tenha sido reiniciado), você poder usar o comando `docker compose start`.
#### Prisma
O Prisma é um [ORM (Object Relational Mapper)](https://www.prisma.io/dataguide/types/relational/what-is-an-orm). Isso significa que ele atua, no caso do projeto, como um **intermediador entre as linguagens SQL e JavaScript** (ele também trabalha com outras linguagens). Assim, podemos focar nas estruturas e códigos no Next, criando tabelas e consultas utilizando objetos em JS, e deixar que o Prisma se responsabilize por se comunicar com o banco de dados e "traduzir" em SQL aquilo que queremos.
- Para adicionar o Prisma ao projeto, usamos o comando `npm i prisma`.
- Para criar os arquivos iniciais para utilização do Prisma, o comando é `npx prisma init`. Caso ele ainda não esteja instalado na máquina, este comando também irá fazer a instalação.
Iniciado o Prisma, uma pasta `prisma` será criada na raiz do projeto com um arquivo `schema.prisma`. Neste arquivo definimos qual SGBD será utilizado e também criamos os objetos que representarão as tabelas e seus relacionamentos (daí o nome *Object Relational Mapper*).
Também será criado um arquivo `.env`, onde são definidas variáveis de ambiente. O Prisma irá consultar esse arquivo para obter as credenciais de conexão ao banco.
> O arquivo `.env` contém dados sensíveis de acesso ao projeto, então **não o versione** nem o compartilhe em ambiente de produção.
#### Criação do banco de dados
Supondo que vamos criar o banco do zero, definimos as tabelas e relacionamentos no arquivo `schema.prisma` e rodamos o comando abaixo para efetuar a chamada "migração" (*migration*). Esta é a ação que irá criar de fato o banco de dados e suas tabelas no Postgres.
npx prisma migrate dev --name init
- `dev` indica que estamos em um ambiente de desenvolvimento;
- `--name init` é a forma de darmos um nome a essa migração, de modo a facilitar identificá-la quando houver outras migrações. Você pode escolher o nome que quiser;
- a pasta `prisma/migrations` contém pastas com os arquivos SQL criados pelo Prisma.
Exemplo de como criar tabelas (models) no `schema.prisma`, incluindo chaves primárias e estrangeira, e relacionamentos:
```prisma
// @id indicates this property as primary key
// @default is a default value; in this case, it will use the autoincrement function to generate an integer
// @unique indicates that the value cannot be repeated between records (rows)
// Post Post[] indicates a 1:N relationship between User and Post
model User {
id Int @id @default(autoincrement())
name String
username String @unique
avatar String
Post Post[]
}// @updatedAt automatically updates the time when a record is updated
// @relation configures foreign keys to indicate a connection between tables
model Post {
id Int @id @default(autoincrement())
cover String
title String
slug String @unique
body String
markdown String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
authorId Int
author User @relation(fields: [authorId], references: [id])
}
```#### Prisma Client
É uma classe disponibilizada pelo Prisma para fazer consultas e as demais operações de CRUD na base de dados, sem usar SQL.
O client é criado pelo seguinte comando:
npx prisma generate
Esse comando deve ser **utilizado toda vez que houver alguma mudança no banco** (alguma alteração no `schema.prisma`), para garantir que o client esteja atualizado com qualquer alteração dos modelos, tipos e relacionamentos.
Para disponibilizar o client para uso na aplicação, uma sugestão é criar um arquivo `db.js` na pasta prisma e exportar o client.
```js
// --- prisma/db.js
import { PrismaClient } from '@prisma/client';
const db = new PrismaClient();
export default db;
```#### Seed de dados
> Link do Prisma sobre seeding, incluindo exemplos em JS e TS: https://www.prisma.io/docs/orm/prisma-migrate/workflows/seeding.
Você pode usar o Prisma para popular (semear) o banco de dados. Para isso, criamos um comando `seed` no `package.json`, e usamos o comando `npx prisma db seed` para popular o banco.
O exemplo a seguir mostra como seria o comando incluído no `package.json`. O arquivo `prisma/seed.js` será executado pelo node e irá popular o banco de dados.
```json
{
"prisma": {
"seed": "node prisma/seed.js"
}
}
```O próximo exemplo mostra a inserção de um novo dado à tabela "Author":
```js
// --- prisma/seed.js
const { PrismaClient } = require('@prisma/client'); // neste caso, precisamos usar require
const prisma = new PrismaClient();async function main() {
// --- creating author "Ana Beatriz"
const author = {
name: "Ana Beatriz",
username: "anabeatriz_dev",
avatar: "https://raw.githubusercontent.com/viniciosneves/code-connect-assets/main/authors/anabeatriz_dev.png",
};// upsert will perform an insert or update in the database
// based on a condition passed to the where property
const ana = await prisma.user.upsert({
where: { username: author.username },
update: {}, // we won't perform any updates for now
create: author
});console.log('Author created: ', ana);
}main()
.then(async () => {
await prisma.$disconnect()
})
.catch(async (e) => {
console.error(e)
await prisma.$disconnect()
process.exit(1)
})
```#### Fetch de dados
Por meio do prisma client, podemos acessar as **tabelas** do banco (os models no `schema.prisma`) como se fossem **objetos do client**. Esses objetos possuem métodos para fazer consultas.
No exemplo a seguir, usamos o método **`findMany`** para recuperar os dados da tabela post. Essa tabela possui uma relação com a tabela author (uma chave estrangeira para o id do autor), então podemos passar via parâmetro um objeto de configuração com a propriedade `include` para também recuperar dados da tabela author. Além disso, também está implementada a lógica para paginação (utilizando as propriedades `take` e `skip`) e a ordenação pela data de criação do post (propriedade `orderBy`);
```js
import db from '../../prisma/db';const ITEMS_PER_PAGE = 6;
// server-side fetch using Prisma client
const getAllPosts = async (page) => {
try {
// logic for previous page
const prev = page > 1 ? page - 1 : null;// logic for next page, based on the number of items in the database
const totalItems = await db.post.count(); // SELECT count(*) FROM post
const totalPages = Math.ceil(totalItems / ITEMS_PER_PAGE);
const next = page < totalPages ? page + 1 : null;// logic to get the items for the next page
const skip = (page - 1) * ITEMS_PER_PAGE;// similar to SELECT * FROM post
const posts = await db.post.findMany({
// use include property to also return data of another table
// when there's a relationship between them (similar to a JOIN)
include: {
author: true
},
// pagination
take: ITEMS_PER_PAGE,
skip,
// sorting
orderBy: { createdAt: 'desc' }
});
return { data: posts, prev, next };
}
catch (error) {
logger.error(`[${new Date().toString()}] Função getAllPosts --> erro de conexão com a API: ${error}`);
return { data: [], prev: null, next: null };
}
}
```#### Deploy FullStack na Vercel
A Vercel possibilita criarmos um banco de dados e integrá-lo ao deploy da aplicação. Para isso, precisamos fazer alguns ajustes no projeto e também na Vercel.
Para o uso do Prisma, a Vercel solicita duas variáveis de ambiente, então precisamos alterar o `schema.prisma`:
```prisma
datasource db {
provider = "postgresql"
// Uses connection pooling
url = env("POSTGRES_PRISMA_URL")
directUrl = env("POSTGRES_URL_NON_POOLING")
}
```Para manter o sincronismo com o projeto rodando localmente, adicionamos essas variáveis também ao `.env`:
```
POSTGRES_PRISMA_URL="postgresql://postgres@localhost:5432/codeconnect_dev"
POSTGRES_URL_NON_POOLING="postgresql://postgres@localhost:5432/codeconnect_dev"
```Por fim, ajustamos o `package.json`, adicionando os passos do Prisma para o script de `build` (a Vercel irá chamar esse comando durante o deploy):
```json
{
"scripts": {
"dev": "next dev",
"build": "prisma generate && prisma migrate dev && prisma db seed && next build",
"start": "next start",
"lint": "next lint"
},
}
```Na Vercel, precisamos adicionar um banco Postgres, disponibilizado pela plataforma. Para o caso deste projeto, a versão free (hobby) está disponível.
- Acesse a página do projeto na Vercel;
- Vá até a aba "Storage";
- Clique no botão "Create" ao lado do item "Postgres";
- Use todas as opções padrão nas próximas janelas.
> A página do projeto na Vercel só aparece após o primeiro deploy. Então faça o primeiro deploy, que irá gerar um erro por não ter um banco de dados, e aí então crie um banco Postgres e faça um redeploy.
### Server Actions
Server Actions são funções assíncronas que você pode criar em seu projeto Next, e que podem ser invocadas tanto por client components quanto por server components. Essas funções são **executadas no lado do servidor**.
Um exemplo comum é invocá-las na submissão de um formulário, usando o atributo `action` do elemento `form`. Um aspecto interessante do Next é que essa submissão **não** irá causar um recarregamento da página.
- Podemos passar argumentos para uma Server Action usando a função `bind`. Isso é necessário, pois a função está rodando no servidor, então temos que "emprestá-la" do servidor para o componente que vai invocá-la;
- Caso uma Server Action resulte em uma ação que atualiza algum campo da página, você pode usar a função `revalidatePath` para que o Next faça as alterações necessárias na UI (sem recarregar a página inteira).
- Server Actions também podem ser invocadas da maneira tradicional, por meio de eventos ou hooks como `useEffect`.
- Podemos utilizar o hook `useFormStatus` do React para verificar se uma ação está pendente. Isso é útil para exibirmos um ícone de carregamento enquanto a ação não termina, por exemplo.
- até 2024, este hook se encontra [disponível de forma experimental](https://react.dev/reference/react-dom/hooks/useFormStatus) no React;
- o hook só funciona se o componente for renderizado dentro de um elemento `form`;
- o componente que utiliza o hook deve ser um client component. Você pode, por exemplo, abstrair o pedaço de código que usa o hook em um componente separado e aí informar que será um client component.
Ao criar uma server action, é uma **boa prática deixar explícito** no arquivo que a função deve ser executada no servidor, utilizando a diretiva `'use server'`.
Outra boa prática é colocar as actions em uma pasta separada. Por exemplo, criar um arquivo `src/actions/index.js` e dentro dele exportar as actions.
Exemplo de Server Action para incrementar o número de curtidas. Observe que usamos o método `update` do Prisma:
```js
// explicit tell Next that this file is to run in the server
'use server';import { revalidatePath } from "next/cache";
import db from "../../prisma/db";// server action to increment the number of likes for a post
export async function incrementThumbsUp(post){
await db.post.update({
where: {
id: post.id
},
data: {
// we can pass an object with an "increment" property
// to let Prisma increment the current value of a field
// by a value of X (increment likes by 1 in this case)
likes: {
increment: 1
}
}
});// clear cache to update the UI of pages affected by this action
revalidatePath('/');
revalidatePath(`/${post.slug}`);
}
```Exemplo de chamada utilizando a `action` de um elemento `form`. Parte não relevante do código foi omitida para economizar espaço.
```jsx
import { incrementThumbsUp } from '@/actions';export const CardPost = ({ post }) => {
// using bind to pass additional arguments to the Server Action
const submitThumbsUp = incrementThumbsUp.bind(null, post);
return (
{post.likes}
);
}
```Mesmo exemplo, dessa vez utilizando o evento de submit do `form`. Neste caso, é necessário transformar o componente em um client component e, por conta disso, impedir o recarregamento da página com `preventDefault`:
```jsx
'use client'import { incrementThumbsUp } from '@/actions';
export const CardPost = ({ post }) => {
// using bind to pass additional arguments to the Server Action
const submitThumbsUp = incrementThumbsUp.bind(null, post);const handleSubmit = e => {
e.preventDefault();
submitThumbsUp();
}
return (
{post.likes}
);
}
```O `ThumbsUpButton` é um client component que vai renderizar um botão ou um spinner, baseado no estado da ação. Parte não relevante do código foi omitida para economizar espaço. :
```jsx
'use client';import { useFormStatus } from "react-dom"
export const ThumbsUpButton = () => {
const { pending } = useFormStatus();
return
{pending ? : }
}
```#### Valores de formulário
Quando uma Server Action é chamada via **`action` de um elemento `form`**, o Next automaticamente **injeta um objeto [`formData`](https://developer.mozilla.org/en-US/docs/Web/API/FormData/FormData)** como último argumento da função. Este objeto contém os valores de cada elemento dentro do formulário, que podem ser acessados por um método `get` passando o atributo `name` desses elementos.
O exemplo abaixo é parte de um código de uma Server Action que faz a inserção de comentários em um post. Observe que `formData` aparece como último parâmetro da função. Esse parâmetro **não** é passado pelo componente que chama a função, e sim injetado automaticamente pelo Next. Também observe que usamos o **método `create`** do Prisma para fazer a **inserção no banco de dados**:
```js
export async function postComment(post, formData) {
await db.comment.create({
data: {
text: formData.get('text'),
authorId: author.id,
postId: post.id
}
});
}
```### Criando endpoints
Além de exibir páginas com o arquivo `pages.js`, você também pode usar a App Router para criar **endpoints no servidor para retornar ou receber dados**, ou seja, usar o Next como uma API para tratar requests e responses.
Para isso, você cria um **arquivo `route.js`**. Dentro deste arquivo, você pode criar funções para os verbos HTTP, como GET, POST, etc. Estas funções possuem dois parâmetros opcionais: o `request`, um [objeto representando a Request](https://nextjs.org/docs/app/api-reference/functions/next-request), e o `context`, um objeto cuja única propriedade atualmente é a `params`, que por sua vez é o objeto que o Next disponibiliza para acessar as rotas dinâmicas.
Um exemplo de organização de projeto é, dentro da pasta `app`, criar uma pasta `api` e nela definir as rotas (endpoints) para lidar com requisições.
O exemplo abaixo cria uma função que irá retornar as respostas a um comentário usando o endpoint `api/comment/[id]/replies`, cujo id é acessado por uma rota dinâmica (`[id]`).
```js
// -- api/comment/[id]/replies/route.js
import db from "../../../../../../prisma/db"// we add underline to indicate that the
// request parameter will not be used
export const GET = async (_request, { params }) => {
const replies = await db.comment.findMany({
where: {
parentId: parseInt(params.id)
},
include: {
author: true
}
});// Response is an interface of the Fetch API
return Response.json(replies);
}
```