-
Notifications
You must be signed in to change notification settings - Fork 0
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
Switch naive database implementation to PostgreSQL #4
Conversation
Co-authored-by: Karl Horky <karl.horky@gmail.com>
app/[id]+api.ts
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- as @ProchaLu mentioned, let's do another PR (stacked on top of this one) to move these to
app/api/
(let's keep the files in place for this PR, to keep the diff simple) - deployment
- where are we suggesting students deploy Expo API Routes + PostgreSQL to?
- let's add a section to the React Native / Expo lecture notes about deployment (let's track that PR / issue also here)
- let's do a video of this and integrate it with the current material
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- created a PR for the folder structure change to
app/api
Restructure to use/api
folder withguestId
#6
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- i. We suggest to deploy the Expo API Routes with PostgreSQL on Vercel.
ii. Created a PR for the Deployment section on the Learning Platform and will create a PR for the Lecture notes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- as @ProchaLu mentioned, let's add a section to the React Native / Expo lecture notes (let's track that PR / issue also here)
- let's do a video of this and integrate it with the current material
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Created a PR for the React Native PostgreSQL lecture notes https://github.com/upleveled/courses/pull/2546
- Created a small video
database/connect.ts
Outdated
function connectOneTimeToDatabase() { | ||
if (!('postgresSqlClient' in globalThis)) { | ||
globalThis.postgresSqlClient = postgres({ | ||
transform: { | ||
...postgres.camel, | ||
undefined: null, | ||
}, | ||
}); | ||
} | ||
|
||
// Use Next.js Dynamic Rendering in all database queries: | ||
// | ||
// Wrap sql`` tagged template function to call `noStore()` from | ||
// next/cache before each database query. `noStore()` is a | ||
// Next.js Dynamic Function, which causes the page to use | ||
// Dynamic Rendering | ||
// | ||
// https://nextjs.org/docs/app/building-your-application/rendering/static-and-dynamic-rendering | ||
return (( | ||
...sqlParameters: Parameters<typeof globalThis.postgresSqlClient> | ||
) => { | ||
return globalThis.postgresSqlClient(...sqlParameters); | ||
}) as typeof globalThis.postgresSqlClient; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As we don't need this noStore
in expo, we may instead connect to the database like in the GraphQL example which is simpler
function connectOneTimeToDatabase() {
if (!globalThis.postgresSqlClient) {
globalThis.postgresSqlClient = postgres(postgresConfig);
}
return globalThis.postgresSqlClient;
}
// Connect to PostgreSQL
export const sql = connectOneTimeToDatabase();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems like the current GraphQL example is missing a few updates from the Next.js example:
database/connect.ts
util/config.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated in this PR
upleveled/graphql-example-spring-2024-atvie#1
Co-authored-by: Karl Horky <karl.horky@gmail.com>
Co-authored-by: Karl Horky <karl.horky@gmail.com>
Co-authored-by: Karl Horky <karl.horky@gmail.com>
app/guests+api.ts
Outdated
export function GET(request: Request): Response { | ||
export async function GET(request: Request): Promise<Response> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there's no way to type the response data of the Expo API response is there?
eg. like NextResponse
in Next.js?
- Accept generic type parameters for
NextResponse
vercel/next.js#45943 - Add optional generic parameter to NextResponse vercel/next.js#47526
if not, we should probably:
- see if it's possible to copy/paste a minimal implementation (eg. this TypeScript Playground version) to the Expo example repo (add to the Expo setup steps) - name could be something like
ExpoApiResponse
- open an Expo issue, if one doesn't already exist
I also created an issue to enforce these type checks in the ESLint config (once we have them):
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generic type parameters for Expo API Routes Issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
database/guests.ts
Outdated
import { Guest } from '../migrations/00000-createTableGuests'; | ||
import { sql } from './connect'; | ||
|
||
export const getGuests = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
const ... = async () =>
arrow function style used here to be closer to the cache()
database query function code we have in the Next.js example:
Co-authored-by: Karl Horky <karl.horky@gmail.com>
app/(tabs)/index.tsx
Outdated
|
||
useFocusEffect( | ||
useCallback(() => { | ||
const getGuests = async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
async function getGuests
instead of arrow function
(for all functions inside useCallback
following this pattern)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to functions
70e748d
app/(tabs)/index.tsx
Outdated
try { | ||
const response = await fetch('/api/guests', { | ||
headers: { | ||
Cookie: 'name=value', | ||
}, | ||
}); | ||
const body: { guests: Guest[] } = await response.json(); | ||
|
||
setGuests(body.guests); | ||
} catch (error) { | ||
console.error('Error fetching guests', error); | ||
} | ||
}; | ||
|
||
getGuests().catch(() => {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- no need for the try/catch - you have the
.catch()
down below - inside of the
.catch()
is where theconsole.error
should be
(for all functions inside useCallback
following this pattern)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
catch the errors now
70e748d
app/(tabs)/newGuest.tsx
Outdated
try { | ||
const body: { error: string } = await response.json(); | ||
if (body.error) { | ||
errorMessage = body.error; | ||
} | ||
} catch {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
another try/catch that is swallowing errors - we should avoid this pattern
we want it to throw if there are errors
(check for all try/catch patterns to see if we need them)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed some try/catch and catch errors
70e748d
app/guests/[guestId].tsx
Outdated
const [edit, setEdit] = useState<boolean>(false); | ||
const [firstName, setFirstName] = useState<string>(''); | ||
const [lastName, setLastName] = useState<string>(''); | ||
const [attending, setAttending] = useState<boolean>(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these all don't need generic args
const [edit, setEdit] = useState<boolean>(false); | |
const [firstName, setFirstName] = useState<string>(''); | |
const [lastName, setLastName] = useState<string>(''); | |
const [attending, setAttending] = useState<boolean>(false); | |
const [edit, setEdit] = useState(false); | |
const [firstName, setFirstName] = useState(''); | |
const [lastName, setLastName] = useState(''); | |
const [attending, setAttending] = useState(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in this commit
02412d3
database/guests.ts
Outdated
export const deleteGuestInsecure = async (id: number) => { | ||
const [guest] = await sql<Guest[]>` | ||
DELETE FROM guests | ||
WHERE | ||
id = ${id} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
export const deleteGuestInsecure = async (id: number) => { | |
const [guest] = await sql<Guest[]>` | |
DELETE FROM guests | |
WHERE | |
id = ${id} | |
export const deleteGuestInsecure = async (guestId: Guest['id']) => { | |
const [guest] = await sql<Guest[]>` | |
DELETE FROM guests | |
WHERE | |
id = ${guestId} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added in this commit
02412d3
app/guests/[guestId].tsx
Outdated
export default function GuestPage() { | ||
const { guestId } = useLocalSearchParams(); | ||
|
||
const [edit, setEdit] = useState<boolean>(false); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since it's a boolean, let's switch to the is
prefix convention:
isEditing
, setIsEditing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Change variable 02412d3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks!
Closes #10
For the Expo React Native Guest List Example, we used a global array to save the guests. This PR updates the example and now uses PostgreSQL with Postgres.js and Ley.
TODO
@upleveled/ley
,dotenv-safe
,postgres
andtsx
topackage.json
util/config.js
anddatabase/connect.ts
filepostgres()
directly instead of using the singleton pattern to test whether Expo API Routes suffer from the same problem with max PostgreSQL connections that Next.js does (check hot reloading) Dev mode hot reload loses database connection vercel/next.js#26427 (comment) - resolution: Expo API Routes also suffer from the same problemglobalThis
var to locallet
var in IIFE closure and test development server Switch globalThis var to local let var in IIFE closure next-js-example-spring-2024-atvie#8 - resolution: IIFE gets re-initialized with hot reloading, we cannot use this