Полностековое приложение Next.js подключается к Cloud SQL PostgreSQL в Google Cloud Run

Я просматриваю документацию по подключению приложения Next.JS в Google Cloud Run к базе данных PostgreSQL по адресу https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/main/cloud-sql/postgres/knex. /README.md.

Я сделал следующее:

  • Создал базу данных PostgreSQL.
  • Создал приложение в Next.js, которое я пообедал в Google Cloud Run.
  • В приложении Google Cloud Run я добавил соединения Cloud SQL в my-project:europe-north1:my-db.
  • В приложении Google Cloud Run я добавил переменные среды:
    • INSTANCE_UNIX_SOCKET=/cloudsql/my-project:europe-north1:my-db
    • INSTANCE_CONNECTION_NAME=мой-проект:Европа-север1:мой-дб
    • DB_NAME=postgres
    • DB_USER=мойпользователь
    • DB_PASS=мой-секретный-пароль пользователя
    • БД_ПОРТ=5432
    • DB_HOST=IP_TO_DB

Я не знаю, как использовать подключение к базе данных, поскольку в Cloud Run появляется следующая ошибка:

⨯ Error: connect ECONNREFUSED 127.0.0.1:5432
    at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1607:16)
    at TCPConnectWrap.callbackTrampoline (node:internal/async_hooks:130:17) {
  errno: -111,
  code: 'ECONNREFUSED',
  syscall: 'connect',
  address: '127.0.0.1',
  port: 5432
}

Мое приложение состоит из страницы, на которой пользователи могут зарегистрироваться. Я постарался сделать это как можно проще, чтобы исправить соединение с базой данных, прежде чем расширять ее.

Страница регистрации

sql/1u.sql

create extension if not exists citext;

create table if not exists public.users (
  id bigserial primary key,
  username citext unique not null,
  password text,
  created_at timestamp default now(),
  updated_at timestamp default now()
);

приложение/(public)/signup/page.tsx

import Form from "./form";

export default async function SignUp() {
  return (
    <div>
      <Form />
    </div>
  );
}

приложение/(public)/signup/form.tsx

"use client";
import React, { FormEvent, useState } from "react";

function Form() {
  const [username, setUsername] = useState<undefined | string>("");
  const [password, setPassword] = useState<undefined | string>("");
  const [confirmPassword, setConfirmPassword] = useState<undefined | string>(
    ""
  );
  const [errors, setErrors] = useState<string[]>([]);

  async function handleSubmit(e: FormEvent) {
    e.preventDefault();
    setErrors([]);

    if (password != confirmPassword) {
      const newErrors = [];
      newErrors.push("Passwords do not match.");
      setErrors(newErrors);
      return;
    }

    const res = await fetch("/api/signup", {
      method: "POST",
      body: JSON.stringify({ username, password }),
    });
    if (res.ok) {
      window.location.href = "/signin";
    } 
    else {
      alert("sign up failed");
    }
  }

  return (
    <form onSubmit = {handleSubmit} className = "flex flex-col gap-2 p-5 max-w-xs w-full bg-slate-800 rounded-lg">

      <div className = "text-center">
        <h3 className = "font-semibold">Sign Up</h3>
      </div>

      <div className = "my-3">
        <hr />
      </div>

      <div>
        <div className = "flex flex-col gap-2">
          <label>Username</label>
          <input className = "text-black p-3 border border-slate-700 rounded-lg" type = "text" onChange = {(e) => setUsername(e.target.value)} value = {username} id = "username" placeholder = "Username" required />
        </div>
      </div>

      <div className = "flex flex-col gap-2 mt-4">
        <label>Password</label>
        <input className = "text-black p-3 border border-slate-700 rounded-lg" type = "password" onChange = {(e) => setPassword(e.target.value)} value = {password} id = "password" placeholder = "Password" required />
      </div>

      <div className = "flex flex-col gap-2 mt-4">
        <label>Confirm Password</label>
        <input className = "text-black p-3 border border-slate-700 rounded-lg" type = "password" onChange = {(e) => setConfirmPassword(e.target.value)} value = {confirmPassword} id = "confirm-password" placeholder = "Confirm Password" required />
      </div>

      <button type = "submit" className = "mt-4 bg-slate-900 text-white p-3 rounded-lg">Sign Up</button>
      
      {errors.map((error) => {
        return (
          <div key = {error} className = "p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400 mt-4" role = "alert">
            <span className = "font-medium">{error}</span>
          </div>
        );
      })}
    </form>
  );
}

export default Form;

приложение/api/signup/route.tsx

import { sql } from "@/db";
import bcrypt from "bcrypt";
import { NextResponse } from "next/server";

export async function POST(request: Request){
    const json = await request.json();

    // Does the username exists?
    const res = await sql(
        "SELECT id, username FROM users WHERE username ILIKE $1",
        [json.username]
    );

    if (res.rowCount! > 0) {
        return NextResponse.json({ error: "user already exists" }, { status: 400 });
    }

    // Genereate user
    const saltRounds = 10;
    const hash = await bcrypt.hash(json.password, saltRounds);

    await sql("INSERT INTO users (username, password) VALUES ($1, $2)", [
            json.username,
            hash,
            ]);

    return NextResponse.json({ msg: "registration success" }, { status: 201 });
}

дб.ц

import { Client, QueryResult } from "pg";
import { loadEnvConfig } from "@next/env";

const projectDir = process.cwd();
loadEnvConfig(projectDir)

// Generate a Postgres client
export async function getClient(): Promise<Client>{
    
    // Client with URL
    if (process.env.DB_URL) {
        const client = new Client({
          connectionString: process.env.DB_URL + "?sslmode=require",
        });
        return client;
    }

    // Client with username, host, database, password
    const client = new Client({
        user: process.env.DB_USER,
        host: process.env.DB_HOST,
        database: process.env.DB_NAME,
        password: process.env.DB_PASS,
        port: parseInt(process.env.DB_PORT!)
    });
    return client;
}

// Handle connection, SQL and end the connection
export async function sql(sql: string, values?: Array<any>): Promise<QueryResult<any>> {
    const client = await getClient();
    await client.connect();
    const res = await client.query(sql, values);
    await client.end();
    return res;
}

Я думаю, мне нужно изменить db.ts на connect-unix.js из примера Google, однако я немного не понимаю, как это сделать..

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
82
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это может быть одним из возможных сценариев и/или решений.

Стандарт PostgreSQL требует наличия суффикса .s.PGSQL.5432 в пути к сокету, как описано в официальном общедоступном документе здесь. Некоторые библиотеки применяют этот суффикс автоматически, но другие требуют указать путь к сокету следующим образом:

/cloudsql/INSTANCE_CONNECTION_NAME/.s.PGSQL.5432

Кажется, в вашем случае этого не хватает:

INSTANCE_UNIX_SOCKET=/cloudsql/my-project:europe-north1:my-db

Таким образом, добавление суффикса .s.PGSQL.5432 для пути к unix-сокету экземпляра Cloud SQL, как показано ниже, может решить проблему.

INSTANCE_UNIX_SOCKET=/cloudsql/my-project:europe-north1:my-db/.s.PGSQL.5432

Другие вопросы по теме