I am trying to use the revalidate function. I tried to follow the code that Vercel offers, but I keep getting an error. Here is the function that I am using:
export async function getServerSideProps() {
const client = await clientPromise;
const db = client.db("myFirstDatabase");
let users = await db.collection("users").find({}).toArray();
users = JSON.parse(JSON.stringify(users));
return {
props: {
users,
},
revalidate: 15,
};
}
And here is the mongodb file that returns the client:
import { MongoClient } from 'mongodb'
const uri = process.env.MONGODB_URI
const options = {
useUnifiedTopology: true,
useNewUrlParser: true,
}
let client
let clientPromise
if (!process.env.MONGODB_URI) {
throw new Error('Please add your Mongo URI to .env.local')
}
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
if (!global._mongoClientPromise) {
client = new MongoClient(uri, options)
global._mongoClientPromise = client.connect()
}
clientPromise = global._mongoClientPromise
} else {
// In production mode, it's best to not use a global variable.
client = new MongoClient(uri, options)
clientPromise = client.connect()
}
export default clientPromise
I have been able to connect to the database and the code works fine if I remove the revalidate part. The error that I get is :
**
Error: Additional keys were returned from getServerSideProps. Properties intended for your component must be nested under the props key, e.g.:
return { props: { title: 'My Title', content: '...' } }
Keys that need to be moved: revalidate.
Read more: https://nextjs.org/docs/messages/invalid-getstaticprops-value
**
I am not sure what I am doing wrong. I want to get data from the database and update it every 15 seconds. Any help would be greatly appreciated.
revalidate is for getStaticProps, you are using it on getServerSideProps and this does not allow
I recommend you to see this library: https://swr.vercel.app/
Related
I need to fetch data from an API that is pretty slow and seldom changes, so I thought I'd use an in-memory cache. I first tried a very simple approach by just saving it to a variable outside the scope of the loader function in my route:
let cache;
export const loader = async () => {
if (!cache) {
// we always end up here
cache = await (await fetch("...)).json()
}
}
but that didn't work. I then tried a proper caching library (lru-cache), but that cache was also always empty. I then realized that the entired file got reloaded on each request which I guess is a dev mode thing, so I tried moving the creation of the cache to a separate file cache.server.ts and importing it from there.
import LRU from "lru-cache";
console.log("Creating cache"); // this is logged on each request
const cache = new LRU({ max: 200 });
export default cache;
But that file also seems to be reloaded on each request.
If I build a production version and run that everything works great, but it would be nice to have some way of getting it to work in dev mode as well.
Remix purges the require cache on every request in development to support <LiveReload/>. To make sure your cache survives these purges, you need to assign it to the global object.
Here's an example from the Jokes Tutorial
import { PrismaClient } from "#prisma/client";
let db: PrismaClient;
declare global {
var __db: PrismaClient | undefined;
}
// this is needed because in development we don't want to restart
// the server with every change, but we want to make sure we don't
// create a new connection to the DB with every change either.
if (process.env.NODE_ENV === "production") {
db = new PrismaClient();
} else {
if (!global.__db) {
global.__db = new PrismaClient();
}
db = global.__db;
}
export { db };
https://remix.run/docs/en/v1/tutorials/jokes#connect-to-the-database
As a follow up on Kilimans answer, here's how I did it:
/*
* #see https://www.npmjs.com/package/node-cache
*/
import NodeCache from "node-cache";
let cache: NodeCache;
declare global {
var __cache: NodeCache | undefined;
}
if (process.env.NODE_ENV === "production") {
cache = new NodeCache();
} else {
if (!global.__cache) {
global.__cache = new NodeCache();
}
cache = global.__cache;
}
export { cache };
And I used it in the loader:
import { getGitHubRepos } from "~/models/github.server";
import { cache } from "~/utils/cache";
export async function loader(args: LoaderArgs) {
if (cache.has("GitHubRepos")) {
return json(cache.get("GitHubRepos"));
}
const repos = await getGitHubRepos();
cache.set("GitHubRepos", repos, 60 * 60 * 24);
return json(repos);
}
I'm using sveltekit and trying to understand all the new features added after retiring Sapper. One of those new features is hooks.js which runs on the server and not accessible to the frontend. It makes dealing with db safe. So I created a connection to my mongodb to retrieve user's data before I use the db results in my getSession function. It works but I noticed that it access my database TWICE. Here is my hooks.js code:
import * as cookie from 'cookie';
import { connectToDatabase } from '$lib/mongodb.js';
export const handle = async ({event, resolve})=>{
const dbConnection = await connectToDatabase();
const db = dbConnection.db;
const userinfo = await db.collection('users').findOne({ username: "a" });
console.log("db user is :" , userinfo) //username : John
const response = await resolve(event)
response.headers.set(
'set-cookie', cookie.serialize("cookiewithjwt", "sticksafterrefresh")
)
return response
}
export const getSession = (event)=>{
return {
user : {
name : "whatever"
}
}
}
The console.log you see here returns the user data twice. One as soon as I fire up my app at localhost:3000 with npm run dev and then less than a second, it prints another console log with the same information
db user is : John
a second later without clicking on anything a second console.log prints
db user is : John
So my understanding from the sveltekit doc is that hooks.js runs every time SvelteKit receives a request. I removed all prerender and prefetch from my code. I made sure I only have the index.svelte in my app but still it prints twice. My connection code I copied from an online post has the following:
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
Here is my connection code:
import { MongoClient } from 'mongodb';
const mongoURI ="mongodb+srv://xxx:xxx#cluster0.qjeag.mongodb.net/xxxxdb?retryWrites=true&w=majority";
const mongoDB = "xxxxdb"
export const MONGODB_URI = mongoURI;
export const MONGODB_DB = mongoDB;
if (!MONGODB_URI) {
throw new Error('Please define the mongoURI property inside config/default.json');
}
if (!MONGODB_DB) {
throw new Error('Please define the mongoDB property inside config/default.json');
}
/**
* Global is used here to maintain a cached connection across hot reloads
* in development. This prevents connections growing exponentially
* during API Route usage.
*/
let cached = global.mongo;
if (!cached) {
cached = global.mongo = { conn: null, promise: null };
}
export const connectToDatabase = async() => {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
useNewUrlParser: true,
useUnifiedTopology: true
};
cached.promise = MongoClient.connect(MONGODB_URI).then((client) => {
return {
client,
db: client.db(MONGODB_DB)
};
});
}
cached.conn = await cached.promise;
return cached.conn;
So my question is : is hooks.js runs twice all the time, one time on the server and one time on the front? If not, then why the hooks.js running/printing twice the db results in my case?
Anyone?
I'm trying to make a GitHub webhook server with Deno, but I cannot find any possible way to do the validation.
This is my current attempt using webhooks-methods.js:
import { Application } from "https://deno.land/x/oak/mod.ts";
import { verify } from "https://cdn.skypack.dev/#octokit/webhooks-methods?dts";
const app = new Application();
app.use(async (ctx, next) => {
try {
await next();
} catch (_err) {
ctx.response.status = 500;
}
});
const secret = "...";
app.use(async (ctx) => {
const signature = ctx.request.headers.get("X-Hub-Signature-256");
if (signature) {
const payload = await ctx.request.body({ type: "text" }).value;
const result = await verify(secret, payload, signature);
console.log(result);
}
ctx.response.status = 200;
});
The verify function is returning false every time.
Your example is very close. The GitHub webhook documentation details the signature header schema. The value is a digest algorithm prefix followed by the signature, in the format of ${ALGO}=${SIGNATURE}:
X-Hub-Signature-256: sha256=d57c68ca6f92289e6987922ff26938930f6e66a2d161ef06abdf1859230aa23c
So, you need to extract the signature from the value (omitting the prefix):
const signatureHeader = request.headers.get("X-Hub-Signature-256");
const signature = signatureHeader.slice("sha256=".length);
Update: Starting in release version 3.0.1 of octokit/webhooks-methods.js, it is no longer necessary to manually extract the signature from the header — that task is handled by the verify function. The code in the answer has been updated to reflect this change.
Here's a complete, working example that you can simply copy + paste into a project or playground on Deno Deploy:
gh-webhook-logger.ts:
import { assert } from "https://deno.land/std#0.177.0/testing/asserts.ts";
import {
Application,
NativeRequest,
Router,
} from "https://deno.land/x/oak#v11.1.0/mod.ts";
import type { ServerRequest } from "https://deno.land/x/oak#v11.1.0/types.d.ts";
import { verify } from "https://esm.sh/#octokit/webhooks-methods#3.0.2?pin=v106";
// In actual usage, use a private secret:
// const SECRET = Deno.env.get("SIGNING_SECRET");
// But for the purposes of this demo, the exposed secret is:
const SECRET = "Let me know if you found this to be helpful!";
type GitHubWebhookVerificationStatus = {
id: string;
verified: boolean;
};
// Because this uses a native Request,
// it can be used in other contexts besides Oak (e.g. `std/http/serve`):
async function verifyGitHubWebhook(
request: Request,
): Promise<GitHubWebhookVerificationStatus> {
const id = request.headers.get("X-GitHub-Delivery");
// This should be more strict in reality
assert(id, "Not a GH webhhok");
const signatureHeader = request.headers.get("X-Hub-Signature-256");
let verified = false;
if (signatureHeader) {
const payload = await request.clone().text();
verified = await verify(SECRET, payload, signatureHeader);
}
return { id, verified };
}
// Type predicate used to access native Request instance
// Ref: https://github.com/oakserver/oak/issues/501#issuecomment-1084046581
function isNativeRequest(r: ServerRequest): r is NativeRequest {
// deno-lint-ignore no-explicit-any
return (r as any).request instanceof Request;
}
const webhookLogger = new Router().post("/webhook", async (ctx) => {
assert(isNativeRequest(ctx.request.originalRequest));
const status = await verifyGitHubWebhook(ctx.request.originalRequest.request);
console.log(status);
ctx.response.status = 200;
});
const app = new Application()
.use(webhookLogger.routes())
.use(webhookLogger.allowedMethods());
// The port is not important in Deno Deploy
await app.listen({ port: 8080 });
I'm reconfiguring my NextJS/Apollo app to allow for SSG with GraphQL API routes, and I'm using this official NextJS starter example as a base for the client config.
I've run into an interesting issue though in my own app, so I've went back to starter example and tried to reproduce it, and was able to. The issue is that without any context object passed into the query resolvers, everything works fine (in the playground and on the client). However, when you introduce a context object and pass it to the resolvers, it works fine in the playground but the context object is undefined when fired from the client. This is the code from the official NextJS starter example, I'll comment where I've added anything.
graphql.js
import { ApolloServer } from "apollo-server-micro";
import { schema } from "../../apollo/schema";
const apolloServer = new ApolloServer({
schema,
context: { //
foo: "bar", // this is the context object I've added
}, //
});
export const config = {
api: {
bodyParser: false,
},
};
export default apolloServer.createHandler({ path: "/api/graphql" });
typedefs.js
import { gql } from '#apollo/client'
export const typeDefs = gql`
type User {
id: ID!
name: String!
status: String!
}
type Query {
viewer: User
}
`
schema.js
import { makeExecutableSchema } from 'graphql-tools'
import { typeDefs } from './type-defs'
import { resolvers } from './resolvers'
export const schema = makeExecutableSchema({
typeDefs,
resolvers,
})
resolvers.js
export const resolvers = {
Query: {
viewer: (_parent, _args, context, _info) => {
console.log("context", context); // console log check that I've added
return { id: 1, name: "John Smith", status: "cached" };
},
},
};
When I run this in the GraphQL playground and query the API, it gives me the correct response, and in my terminal console it returns the context foo: bar object from the console log, so in the server the context object is being passed correctly. However, when I visit the index page in the browser, which is this:
index.js
import gql from "graphql-tag";
import Link from "next/link";
import { useQuery } from "#apollo/client";
import { initializeApollo } from "../apollo/client";
const ViewerQuery = gql`
query ViewerQuery {
viewer {
id
name
status
}
}
`;
const Index = () => {
const {
data: { viewer },
} = useQuery(ViewerQuery);
return (
<div>
You're signed in as {viewer.name} and you're {viewer.status} goto{" "}
<Link href="/about">
<a>static</a>
</Link>{" "}
page.
</div>
);
};
export async function getStaticProps() {
const apolloClient = initializeApollo();
await apolloClient.query({
query: ViewerQuery,
});
return {
props: {
initialApolloState: apolloClient.cache.extract(),
},
};
}
export default Index;
...the viewer name and viewer status are rendered, so the query is actually happening, but in the console, the context object console log is returning undefined. So when used in the client, the context is being lost somehow. I find this interesting, since this is an official NextJS starter example, and unless they've set up the client to not accept context in the resolvers, I can't see what the problem is. And if it is the case that the client is not set up to accept context, is there any other official examples with a client setup that does?
This is a long question now, but here is the client.js setup:
import { useMemo } from "react";
import { ApolloClient, InMemoryCache } from "#apollo/client";
let apolloClient;
function createIsomorphLink() {
if (typeof window === "undefined") {
const { SchemaLink } = require("#apollo/client/link/schema");
const { schema } = require("./schema");
return new SchemaLink({ schema });
} else {
const { HttpLink } = require("#apollo/client/link/http");
return new HttpLink({
uri: "http://localhost:3000/api/graphql",
credentials: "same-origin",
});
}
}
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === "undefined",
link: createIsomorphLink(),
cache: new InMemoryCache(),
});
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient();
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
_apolloClient.cache.restore(initialState);
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === "undefined") return _apolloClient;
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient;
return _apolloClient;
}
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState]);
return store;
}
I implore anyone to clone this official repo and see if they can figure out how to get context working in the client, or if anyone knows why this client setup isn't working for context and knows a client setup that does accept resolver context, I would appreciate it. This problem has cost me two days now!
I've figured out the problem. The client configuration is using SchemaLink for the http request, and the context is passed in the SchemaLink constructor function, not in the server options, because context is passed in the http headers with httpLink.
People, how are you? I have a query, I just implemented my API made with apollo server in an AWS Lambda. I used the official documentation as a guide, but I'm noticing that the context handling varies a bit. I have a doubt with the latter, since I made certain changes and everything works fine locally using "serverless offline", but once I deploy it doesn't. Apparently the authentication context that I generate does not finish reaching my query. If someone can guide me a bit with this, I will be very grateful.
This is my API index:
const { ApolloServer, gql } = require('apollo-server-lambda');
const typeDefs = require('./db/schema');
const resolvers = require('./db/resolvers');
const db = require('./config/db');
const jwt = require('jsonwebtoken');
require('dotenv').config({ path: 'variables.env' });
db.conectDB();
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: "/graphql"
},
context: ({ event, context }) => {
try {
const token = event.headers['authorization'] || '';
if(token){
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);
}
return {
headers: event.headers,
functionName: context.functionName,
event,
context,
}
} catch (error) {
console.error(error);
}
}
});
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
This is my query:
getUserByToken: async (_, {}, { context }) => {
if(context)
throw new Error((context ? 'context' : '') + ' ' + (context.user ? 'user' : ''));
let user = await db.findOne('users',{ _id: ObjectId(context.user._id) });
if(user.birthdate)
user.birthdate = user.birthdate.toString();
if(user.password)
user.password = true;
else
user.password = false;
return user;
}
My API response:
API response
From what I can see, you're not calling getUserByToken in your context. Is that correct? So, I'm not sure how you're encountering this error.
Can I give you some pointers?
Connecting to your DB is probably (or it should be) asynchronous. For that, I'd run your code like this:
db.connect()
.then(() => {
... handle your request in here
})
.catch(console.error);
I think you meant to call your getUserByToken in this line:
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);