Server-side authentication for external services

Hi all! I am looking at building a program that queries Google Earth Engine for forest data in a carbon project, and looking at how I could access their api using a generic account for all users. Their documentation: https://developers.google.com/earth-engine/guides/npm_install describe the process to initialize the client from the server. I have not done this before in a monorepo, so I wonder where the best place to do this within a vanilla T3 app structure? Since I am using a generic account, I am thinking I only want to run this once when the T3 app starts.
17 Replies
Keef
Keef17mo ago
I generally spin off singletons like the prisma client if you go to the file it’s instantiated in. Then just import when needed. That should work for this And I leave it in the /server directory under a file like earth-api yadda yadda Can folder it too if you want
peternovak
peternovak17mo ago
Thanks @keef (Rustular CVO) , that sounds like a nice approach! I realize this question sounds very nooby, but from where should I call this function? I noted in this thread that some people have configured their next.config.js file for this: https://github.com/vercel/next.js/discussions/15054 But the one in vanilla T3 does not call prisma (so that must be initiated elsewhere). I imagine it would be good to have all this at one place?
GitHub
Global variables · vercel next.js · Discussion #15054
Hello! I would like to create a variable in server.js and use it everywhere in application. I need to make a call to an API and get a list (with categories) which I will use in my application. The ...
Keef
Keef17mo ago
It gets initialized at some point but I don't know the exact terminology. Its definitely implicit If you want a more explicit definition you can do something like this
let prisma;

export const getDatabase() {
if(!prisma) {
prisma = new PrismaClient({})
}

return prisma
}
let prisma;

export const getDatabase() {
if(!prisma) {
prisma = new PrismaClient({})
}

return prisma
}
Then just call getDatabase where you need it Thats free handed so it might not be 100% correct but the gist is its lazy initialized once you try to use it once In comparison to how I have mine currently set up which is off the t3 turbo boilerplate
import { PrismaClient } from "@prisma/client";

import { env } from "~/env.mjs";

const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};

export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log:
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});

if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
import { PrismaClient } from "@prisma/client";

import { env } from "~/env.mjs";

const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};

export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log:
env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"],
});

if (env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
Actually its basically the same thing. Once you try to use prisma like in context for trpc, it'll initialize it for you Create context is likely the first usage of prisma so it'll be defined in that area
// This will basically initialize it for us
import { prisma } from "db";

// This will allow us to use it throughout our routes
const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
};
};
// This will basically initialize it for us
import { prisma } from "db";

// This will allow us to use it throughout our routes
const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
};
};
Some of their uses cases in that github link are a little different
peternovak
peternovak17mo ago
Oh, thanks, I was just thinking I would initiate Earth Engine at the same place, but maybe it is overkill to change Prisma Where would you call the EarthEngine singleton in a vanilla T3 setup?
Keef
Keef17mo ago
You are using it to query with yeah? Its just another thing like prisma, just a controller/api for us to consume in our queries/mutations so I'd probably toss it in the trpc context if thats what i'll be doing with it
peternovak
peternovak17mo ago
Yes I use to to query, but I only need to initiate it once for all users
Keef
Keef17mo ago
Yeah just do the same thing as initializing prisma one sec I can write it for you and it'll probably do the job? Oh man this thing doesn't have types available what a pos
peternovak
peternovak17mo ago
Yeah, typescript is furious at me at the moment :/
Keef
Keef17mo ago
// earth-api.ts
import ee from '@google/earthengine';
import { env } from '~/env.mjs';

const globalForEarthEngine = globalThis as unknown as {
earthEngine: ee | undefined;
};

export const earthEngine =
globalForEarthEngine.earthEngine ??
ee.data.authenticateViaPrivateKey(
env.EE_KEY,
() => {
// success authentication
ee.initialize(
null,
null,
() => {
// success init
},
() => {
// fail init
},
);
},
() => {
// fail authentication
},
);
// earth-api.ts
import ee from '@google/earthengine';
import { env } from '~/env.mjs';

const globalForEarthEngine = globalThis as unknown as {
earthEngine: ee | undefined;
};

export const earthEngine =
globalForEarthEngine.earthEngine ??
ee.data.authenticateViaPrivateKey(
env.EE_KEY,
() => {
// success authentication
ee.initialize(
null,
null,
() => {
// success init
},
() => {
// fail init
},
);
},
() => {
// fail authentication
},
);
// trpc.ts or wherever you define the context
import { earthEngine } from "../earth-api";

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
earthEngine
};
};
// trpc.ts or wherever you define the context
import { earthEngine } from "../earth-api";

const createInnerTRPCContext = (opts: CreateContextOptions) => {
return {
session: opts.session,
prisma,
earthEngine
};
};
// random router
const someRouter = router({
carbon: publicProcedure.query(async({ctx}) => {
// ctx.earthEngine should be available to you here now
}),
})
// random router
const someRouter = router({
carbon: publicProcedure.query(async({ctx}) => {
// ctx.earthEngine should be available to you here now
}),
})
Haven't had too much experience consuming non typed js libs in typescript so I won't be much use here but that should work? Basically we define the interface once in earth-api.ts whenever we try to use it we check global.earthEngine to see if it exists and if it doesn't we'll initialize it then give it to who needs it
peternovak
peternovak17mo ago
Cool, thanks, let me try!!
Keef
Keef17mo ago
fingers crossed 🤞 this check might also be necessary in your earth-api.ts
if (env.NODE_ENV !== "production") globalForEarthEngine.earthEngine = earthEngine;
if (env.NODE_ENV !== "production") globalForEarthEngine.earthEngine = earthEngine;
for non production since hot module reload can sometimes spawn a lot of the same thing when you are doing dev work for production we initialize it once in context so its safe to leave out
peternovak
peternovak17mo ago
I tried to implement a rough non-typesafe version just to see if to would work:
peternovak
peternovak17mo ago
But I guess I am using 'ee' in the wrong way:
peternovak
peternovak17mo ago
Given how the 'ee' object looks like, could there be some better way to wrap in my backend?
peternovak
peternovak17mo ago
Wait, let me do one more test.
peternovak
peternovak17mo ago
I placed my promises in the wrong order. Just did a test with this code and despite the typings are horrible, it seams to work!!
peternovak
peternovak17mo ago
Let me try some real API calls and see if it works there! So far so good - thanks a lot @keef (Rustular CVO) , this looks really promising!!
Want results from more Discord servers?
Add your server