Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update hackernews app to use drizzle and postgres #3329

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 45 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@ jobs:
- name: Build Packages
run: pnpm build

- name: patch compose file volumes
uses: mikefarah/[email protected]
with:
cmd: yq -i 'del(.services.*.volumes)' examples/hackernews/docker-compose.yml

- name: Start Docker
run: docker compose -f examples/hackernews/docker-compose.yml up -d --wait postgres

- name: Run Tests
uses: nick-fields/retry@v3
with:
Expand Down Expand Up @@ -263,6 +271,43 @@ jobs:
failOnRequired: true
debug: true

hackernews-docker-container:
runs-on: ubuntu-latest
env:
DOCKER_REGISTRY: ghcr.io/${{ github.repository }}/
DOCKER_TAG: :${{ github.event.pull_request.head.sha }}
steps:
- name: Checkout Repository
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4

- name: Setup env
uses: the-guild-org/shared-config/setup@main
with:
nodeVersion: 22
packageManager: pnpm

- name: Build Packages
run: pnpm build

- name: Build Hackernews App
run: pnpm --filter=example-hackernews build

- name: Isolate Docker Image Build Context
run: pnpm build:hackernews:docker

- name: Build Hackernews Docker Image
run: docker compose -f .hackernews-deploy/docker-compose.yml build api

- name: Log in to the Container registry
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Push Docker Image
run: docker compose -f .hackernews-deploy/docker-compose.yml push api

# TODO: have the example and packages use singleton nestjs dependencies
# but without using .pnpmfile.cjs because it causes issues with renovate: https://github.com/dotansimha/graphql-yoga/pull/2622
# nestjs-apollo-federation-compatibility:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ packages/graphql-yoga/src/graphiql-html.ts
.tool-versions

.mise.toml
.hackernews-deploy
1 change: 1 addition & 0 deletions examples/hackernews/.env.sample
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PG_CONNECTION_STRING=postgres://postgres:postgres@localhost:5432/postgres
3 changes: 1 addition & 2 deletions examples/hackernews/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
node_modules
# Keep environment variables out of version control
.env
prisma/migrations/migration_lock.toml
prisma/*.db*
.pg-data
23 changes: 23 additions & 0 deletions examples/hackernews/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
FROM node:22-slim

RUN apt-get update && apt-get install -y ca-certificates

WORKDIR /usr/src/app

COPY ./package.json /usr/src/app
COPY ./node_modules /usr/src/app/node_modules

COPY ./dist/src/ /usr/src/app/

LABEL org.opencontainers.image.title="Hackernews GraphQL Server"
LABEL org.opencontainers.image.version=$RELEASE
LABEL org.opencontainers.image.description="A Hackernews clone built with GraphQL and graphql-yoga."
LABEL org.opencontainers.image.authors="The Guild"
LABEL org.opencontainers.image.vendor="Laurin Quast"
LABEL org.opencontainers.image.url="https://github.com/dotansimha/graphql-yoga/tree/main/examples/hackernews"
LABEL org.opencontainers.image.source="https://github.com/dotansimha/graphql-yoga/tree/main/examples/hackernews"

ENV ENVIRONMENT production
ENV RELEASE $RELEASE

ENTRYPOINT ["node", "main.js"]
2 changes: 1 addition & 1 deletion examples/hackernews/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
## Stack

- GraphQL Yoga
- Prisma
- Drizzle

## Tutorial

Expand Down
84 changes: 39 additions & 45 deletions examples/hackernews/__integration-tests__/hackernews.spec.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,51 @@
import path from 'node:path';
import { resolve } from 'node:path';
import { sql } from 'drizzle-orm';
import { drizzle, NodePgDatabase } from 'drizzle-orm/node-postgres';
import { migrate } from 'drizzle-orm/postgres-js/migrator';
import { createYoga, YogaServerInstance } from 'graphql-yoga';
import { PrismaClient } from '@prisma/client';
import { DbDrop, MigrateDev } from '@prisma/migrate';
import type { GraphQLContext } from '../src/context';
import { schema } from '../src/schema';
import pg from 'pg';
import type { GraphQLContext } from '../src/context.js';
import { schema } from '../src/schema.js';

const connectionString =
process.env['PG_CONNECTION_STRING'] ??
'postgres://postgres:postgres@localhost:5432/postgres?currentSchema=integrationTests';

async function resetDatabase(db: NodePgDatabase, schema: string) {
// sql query for resetting the database
const query = sql`
DROP SCHEMA IF EXISTS ${sql.raw(schema)} CASCADE;
CREATE SCHEMA ${sql.raw(schema)};
`;
await db.execute(query);
}

describe('hackernews example integration', () => {
let yoga: YogaServerInstance<GraphQLContext, GraphQLContext>;
let db: NodePgDatabase;
let client: pg.Client;
let currentSchema: string;
beforeAll(async () => {
const { createContext } = await import('../src/context');
yoga = createYoga({ schema, context: createContext });

// migrate
await MigrateDev.new().parse([
`--schema=${path.resolve(__dirname, '..', 'prisma', 'schema.prisma')}`,
]);

// seed
const client = new PrismaClient();
await client.link.create({
data: {
url: 'https://www.prisma.io',
description: 'Prisma replaces traditional ORMs',
},
const url = new URL(connectionString);
const cs = url.searchParams.get('currentSchema');
if (!cs) {
throw new Error("Must provide 'currentSchema' in the connection string");
}
currentSchema = cs;
client = new pg.Client(connectionString);
await client.connect();
db = drizzle(client);
await resetDatabase(db, currentSchema);
await migrate(drizzle(client), {
migrationsSchema: currentSchema,
migrationsFolder: resolve(__dirname, '../src/drizzle'),
});
await client.$disconnect();
yoga = createYoga({ schema, context: { db } });
});

afterAll(async () => {
// drop
await DbDrop.new().parse([
`--schema=${path.resolve(__dirname, '..', 'prisma', 'schema.prisma')}`,
'--preview-feature', // DbDrop is an experimental feature
'--force',
]);
});

it('should get posts from feed', async () => {
const response = await yoga.fetch('http://yoga/graphql?query={feed{url,description}}');

const body = await response.json();
expect(body).toMatchInlineSnapshot(`
{
"data": {
"feed": [
{
"description": "Prisma replaces traditional ORMs",
"url": "https://www.prisma.io",
},
],
},
}
`);
await resetDatabase(db, currentSchema);
await client.end();
});

it('should create a new post', async () => {
Expand Down
17 changes: 0 additions & 17 deletions examples/hackernews/__integration-tests__/prisma__migrate.d.ts

This file was deleted.

42 changes: 42 additions & 0 deletions examples/hackernews/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: '3.8'
services:
postgres:
image: postgres:14.12-alpine
networks:
- 'stack'
healthcheck:
test: ['CMD-SHELL', 'pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}']
interval: 10s
timeout: 5s
retries: 5
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postgres
PGDATA: /var/lib/postgresql/data
volumes:
- ./.pg-data:/var/lib/postgresql/data
ports:
- '5432:5432'

api:
image: ${DOCKER_REGISTRY}yoga-hackernews${DOCKER_TAG}
build:
context: .
dockerfile: Dockerfile
networks:
- 'stack'
depends_on:
- postgres
environment:
PG_CONNECTION_STRING: postgres://postgres:postgres@postgres:5432/postgres
ports:
- '4000:4000'
healthcheck:
test: ['CMD', 'curl', '-f', 'http://localhost:3000/health']
interval: 10s
timeout: 5s
retries: 5

networks:
stack: {}
18 changes: 18 additions & 0 deletions examples/hackernews/drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
dialect: 'postgresql',
out: './src/drizzle',
schema: './src/drizzle/schema.ts',
dbCredentials: {
host: process.env.DB_HOST!,

Check warning on line 8 in examples/hackernews/drizzle.config.ts

View workflow job for this annotation

GitHub Actions / typecheck

Forbidden non-null assertion
port: Number(process.env.DB_PORT!),

Check warning on line 9 in examples/hackernews/drizzle.config.ts

View workflow job for this annotation

GitHub Actions / typecheck

Forbidden non-null assertion
user: process.env.DB_USERNAME!,

Check warning on line 10 in examples/hackernews/drizzle.config.ts

View workflow job for this annotation

GitHub Actions / typecheck

Forbidden non-null assertion
password: process.env.DB_PASSWORD!,

Check warning on line 11 in examples/hackernews/drizzle.config.ts

View workflow job for this annotation

GitHub Actions / typecheck

Forbidden non-null assertion
database: process.env.DB_NAME!,

Check warning on line 12 in examples/hackernews/drizzle.config.ts

View workflow job for this annotation

GitHub Actions / typecheck

Forbidden non-null assertion
},
// Print all statements
verbose: true,
// Always ask for confirmation
strict: true,
});
32 changes: 21 additions & 11 deletions examples/hackernews/package.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,39 @@
{
"name": "example-hackernews",
"version": "4.3.0",
"description": "",
"author": "",
"license": "ISC",
"type": "module",
"description": "Hackernews API Clone using GraphQL Yoga.",
"author": "Laurin Quast",
"license": "MIT",
"private": true,
"files": [
"Dockerfile",
"dist/src",
"docker-compose.yml",
"package.json",
"README.md"
],
"keywords": [],
"scripts": {
"build": "tsc",
"check": "tsc --pretty --noEmit",
"dev": "cross-env NODE_ENV=development ts-node-dev --exit-child --respawn src/main.ts",
"migrate": "prisma migrate dev",
"postinstall": "prisma generate",
"dev": "cross-env NODE_ENV=development node --loader=ts-node/esm --env-file .env --watch src/main.ts",
"migrate": "pnpm drizzle-kit generate",
"postbuild": "npx cpy 'src/**/*.{json,sql}' ./dist/src --parents",
"start": "ts-node src/main.ts"
},
"dependencies": {
"drizzle-orm": "0.31.2",
"graphql": "16.6.0",
"graphql-yoga": "workspace:*"
"graphql-yoga": "workspace:*",
"pg": "8.12.0"
},
"devDependencies": {
"@prisma/client": "5.13.0",
"@prisma/internals": "5.13.0",
"@prisma/migrate": "5.13.0",
"@types/node": "18.16.16",
"@types/pg": "8.11.6",
"cpy-cli": "5.0.0",
"cross-env": "7.0.3",
"prisma": "5.13.0",
"drizzle-kit": "0.22.7",
"ts-node": "10.9.1",
"ts-node-dev": "2.0.0",
"typescript": "5.1.6"
Expand Down

This file was deleted.

This file was deleted.

27 changes: 0 additions & 27 deletions examples/hackernews/prisma/schema.prisma

This file was deleted.

12 changes: 2 additions & 10 deletions examples/hackernews/src/context.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,5 @@
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';

export type GraphQLContext = {
prisma: PrismaClient;
db: NodePgDatabase;
};

export async function createContext(): Promise<GraphQLContext> {
return {
prisma,
};
}
Loading
Loading