My stack is:
Apollo Server,
graphql,
prisma,
nextjs
I have added a resolver.ts and schema.ts for my graphql config under /graphql
resolver.ts
export const resolvers = {
Query: {
books: () => books,
},
};
const books = [
{
title: 'The Awakening',
author: 'Kate Chopin',
},
{
title: 'City of Glass',
author: 'Paul Auster',
},
];
schema.ts
import { gql } from "apollo-server-micro";
export const typeDefs = gql`
# This "Book" type defines the queryable fields for every book in our data source.
type Book {
title: String
author: String
}
# The "Query" type is special: it lists all of the available queries that
# clients can execute, along with the return type for each. In this
# case, the "books" query returns an array of zero or more Books (defined above).
type Query {
books: [Book]
}
/pages/api/graphql.ts
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { ApolloServer } from 'apollo-server-micro';
import { typeDefs } from '../../graphql/schema';
import { resolvers } from '../../graphql/resolver';
const apolloServer = new ApolloServer ({typeDefs, resolvers});
const startServer = apolloServer.start();
export default async function handler(req, res) {
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader(
'Access-Control-Allow-Origin',
'https://studio.apollographql.com'
);
res.setHeader(
'Access-Control-Allow-Headers',
'Origin, X-Requested-With, Content-Type, Accept'
);
if (req.method === 'OPTIONS') {
res.end();
return false;
}
await startServer;
await apolloServer.createHandler({
path: "/api/graphql",
})(req, res);
}
export const config = {
api: {
bodyParse: false
}
}
When I navigate to my api endpoint /api/graphql it takes me to the apollo studio explorer but its not picking up the endpoint or the schema. The errors in dev tools seem to be studio libraries specifically they dont seem very helpful:
StaleWhileRevalidate.js:112 Uncaught (in promise) no-response: no-response :: [{"url":"https://cdn.segment.com/analytics.js/v1/xPczztcxJ39mG3oX3wle6XlgpwJ62XAA/analytics.min.js"}]
at O._handle (https://studio.apollographql.com/service-worker.js:2:71211)
at async O._getResponse (https://studio.apollographql.com/service-worker.js:2:47966)
_handle # StaleWhileRevalidate.js:112
useTelemetryInitializer.ts:174 GET https://cdn.segment.com/analytics.js/v1/xPczztcxJ39mG3oX3wle6XlgpwJ62XAA/analytics.min.js net::ERR_FAILED
I don't think its anything to do with prisma as all I have done is set up a postgresql db and defined some basic schema. Dont see why studio is not picking my endpoint, it doesn't seem to be CORS related as im not getting cross origin errors.
Studio screenshot:
I'm sorry if is too late, but the one thing that fixed this for me was cleaning the storage of the website
Related
I'm trying to create a middleware to ensure the user is admin, but when I print request.customer, the result is undefined.
This is what I'm doing:
import { NextFunction, Request, Response } from "express";
import { prisma } from "../../prisma";
export async function ensureAdmin(
request: Request,
response: Response,
next: NextFunction
) {
const { id } = request.customer;
const customer = await prisma.customer.findUnique({
where: {
id,
},
});
console.log(request.customer);
return next();
}
request.customer returns this: Property 'customer' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'
So I declared the express namespace by adding the customer id, like this:
declare namespace Express {
export interface Request {
customer: {
id: string;
}
}
}
I have no errors but at the same time when I print request.customer it comes undefined.
I think you need to correctly implement the middleware as it is described in this documentation: Using middleware.
There you will have the (req, res, next) function. The req is of type Request and contains parameters for query parameters (called via req.params), headers (called via req.headers['...']) and many more. Wherever your customer id is encoded, you need to extract it from your request.
Getting this error in Next.js _middleware file when I try to initialize Firebase admin V9. Anyone know how to solve this issue?
./node_modules/#google-cloud/storage/build/src/bucket.js:22:0
Module not found: Can't resolve 'fs'
../../firebase/auth-admin
import * as admin from "firebase-admin";
if (!admin.apps.length) {
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_ADMIN_PRIVATE_KEY,
}),
});
}
const firestore = admin.firestore();
const auth = admin.auth();
export { firestore, auth };
Calling it in my _middleware
import { NextFetchEvent, NextRequest, NextResponse } from "next/server";
import { auth } from "../../firebase/auth-admin";
export default async function authenticate(
req: NextRequest,
ev: NextFetchEvent
) {
const token = req.headers.get("token");
console.log("auth = ", auth);
// const decodeToken = await auth.verifyIdToken(token);
return NextResponse.next();
}
I saw a solution here by customizing webpack but this does not fix it.
/** #type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
webpack: (config, { isServer, node }) => {
node = {
...node,
fs: "empty",
child_process: "empty",
net: "empty",
tls: "empty",
};
return config;
},
};
module.exports = nextConfig;
The Edge Runtime, which is used by Next.js Middleware, does not support Node.js native APIs.
From the Edge Runtime documentation:
The Edge Runtime has some restrictions including:
Native Node.js APIs are not supported. For example, you can't read or write to the filesystem
Node Modules can be used, as long as they implement ES Modules and do not use any native Node.js APIs
You can't use Node.js libraries that use fs in Next.js Middleware. Try using a client-side library instead.
I wasted a lot of time tying to get this to work. The weird thing is that this will work in the api itself.
So instead of calling firebase-admin action in the _middleware file. Call it in the api itself like:
import type { NextApiRequest, NextApiResponse } from 'next'
import { auth } from "../../firebase/auth-admin";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const authorization = req.headers.authorization
console.log(`Handler auth header: ${authorization}`)
if (!authorization) {
return res.status(401).json({ message: 'Authorisation header not found.' })
}
const token = authorization.split(' ')[1]
if (!token) {
return res.status(401).json({ message: 'Bearer token not found.' })
}
console.log(`Token: ${token}`)
try {
const {uid} = await auth.verifyIdToken("sd" + token)
console.log(`User uid: ${uid}`)
res.status(200).json({ userId: uid })
} catch (error) {
console.log(`verifyIdToken error: ${error}`)
res.status(401).json({ message: `Error while verifying token. Error: ${error}` })
}
}
A workaround to make this reusable is to create a wrapper function.
If anyone knows how to make this work in a _middleware file, I would be really grateful.
Edit: Gist for the wrapper middleware function:
https://gist.github.com/jvgrootveld/ed1863f0beddc1cc2bf2d3593dedb6da
make sure you're not calling firebase-admin in the client
import * as admin from "firebase-admin";
I've recently released a library that aims to solve the problem: https://github.com/ensite-in/next-firebase-auth-edge
It allows to create and verify tokens inside Next.js middleware and Next.js 13 server components. Built entirely upon Web Crypto API.
Please note it does rely on Next.js ^13.0.5 experimental "appDir" and "allowMiddlewareResponseBody" features.
Here's my problem:
I have an API in NestJS, it is hosted on Firebase (Cloud functions).
My client is an Ionic - Angular web interface.
"#nestjs/cli": "^7.5.1",
"#types/node": "^14.14.6",
"typescript": "^4.0.5"
When I test the application locally with the command npm run start everything works fine.
When I test in production with firebase serve --only functions or firebase deploy --only functions, I get the following error (on browser) when launching the server :
Some of them are good, the other not.
here is a bad one:
and a good one:
Once the first slave of requests is made, everything will run again for the next 5 minutes.
You'll tell me that it's not serious, I just have to make the requests when I put the API in production and the users will be able to use it normally.
Except that when the functions are not used for about 5 minutes, the API in production pauses, then it restarts as soon as it receives a request. This behavior seems normal to me, but because of errors and therefore inconsistencies in the results on the interface.
Here is my index.ts on my NestJS api:
const server = express();
export const createNestServer = async (expressInstance) => {
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
const whitelist = [
'http://localhost:8100/',
'http://localhost:8100',
'*',
undefined,
];
app.enableCors({
origin: function (origin, callback) {
if (whitelist.indexOf(origin) !== -1) {
console.log('allowed cors for:', origin);
callback(null, true);
} else {
console.log('blocked cors for:', origin);
callback(new Error('Not allowed by CORS'));
}
},
allowedHeaders:
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe',
methods: 'GET, OPTIONS',
credentials: true,
preflightContinue: true,
optionsSuccessStatus: 200,
});
return app.init();
}
createNestServer(server)
.then((v) => console.log('Nest Ready'))
.catch((err) => console.error('Nest broken', err));
export const api = functions.https.onRequest(server);
My question is this: How can I make sure that the cors are activated from the beginning when the server is launched and do not return an error at launch?
SOLUTION :
Thanks to NeatNerd, here is the solution :
Tutorial to follow : https://softwarebrothers.co/blog/setting-up-nestjs-with-firebase-functions/
my new index.ts :
import { NestFactory } from '#nestjs/core';
import { ExpressAdapter } from '#nestjs/platform-express';
import { AppModule } from './app.module';
import * as express from 'express';
import * as functions from 'firebase-functions';
const server = express();
export const createNestServer = async (expressInstance) => {
const app = await NestFactory.create(
AppModule,
new ExpressAdapter(expressInstance),
);
const whitelist = [
'http://localhost:8100/',
'http://localhost:8100',
'*',
undefined,
];
app.enableCors({
origin: function (origin, callback) {
console.log(origin);
if (whitelist.filter((x) => x && x.startsWith(origin))) {
console.log('The CORS policy for this site allow access from ', origin);
callback(null, true);
} else {
console.log(
'\n\n\nThe CORS policy for this site does not allow access from ',
origin,
);
callback(
new Error(
'\n\n\n\n\n The CORS policy for this site does not allow access from ' +
origin,
),
false,
);
}
},
allowedHeaders:
'X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept, Observe',
methods: 'GET, OPTIONS',
credentials: true,
preflightContinue: true,
optionsSuccessStatus: 200,
});
return app.init();
};
createNestServer(server)
.then((v) => console.log('Nest Ready'))
.catch((err) => console.error('Nest broken', err));
export const api = functions
.region('us-central1')
.https.onRequest(async (request, response) => {
await createNestServer(server);
server(request, response);
});
I was missing the reference to createNestServer for each call. The tutorial that allowed me to set it up doesn't have this part. However, it is still quite complementary. https://fireship.io/snippets/setup-nestjs-on-cloud-functions/
The following option is also part of the solution : (keepConnectionAlive)``
export const config: TypeOrmModuleOptions = {
type: 'mysql',
port: 3306,
host: 'xxx.xxx.xxx.xxxx',
database: 'name_of_the_database',
username: 'username',
password: 'password',
synchronize: false,
entities: [Entity1, Entity2],
autoLoadEntities: true,
keepConnectionAlive: true,
};
This allows to keep the connexion open.
You need to do two things:
Setup NestJS with Firebase functions properly. Here is an example of how to do it.
Check Firebase logs either in console or by running firebase functions:log. I am pretty sure there is something else going on
When you start the deployment, the old function is up and running until the deployment is finished, so there will no disruption of service.
Additionally, since the error actually pointing out to CORS, you want also to have a look at how to set up this.
I am new to graphql. I am building a react site with graphql on the backend. I am trying to figure out how to limit the number of objects (or items) returned by the resolver. Documentation is very scarce and i did not see clear examples for filtering or limiting or sorting things.
my index file looks like this:
import express from "express";
import mongoose from "mongoose";
import {ApolloServer, gql} from "apollo-server-express";
import {resolvers} from "./resolver"
import {typeDefs} from "./typeDefs"
const bodyParser = require('body-parser');
const server = async () => {
const app = express();
app.use(bodyParser.json());
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'POST,GET,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
const server = new ApolloServer({
typeDefs,
resolvers
})
server.applyMiddleware({app})
await mongoose.connect("mongoaddr...",{useNewUrlParser:true})
app.get('/', (req,res)=> res.send('hello'))
app.listen({port:4001}, ()=>{
console.log('connected')
})
}
server()
My model:
import mongoose from "mongoose";
export const Post = mongoose.model("Post",{
word:String,
)
my typeDefs:
import {gql} from "apollo-server-express";
export const typeDefs = gql`
type Query{
getPosts(limit:Int):[Post!]!
}
type Post {
id:ID!
word: String!
}
And my resolver:
import { Post } from "./models/Post";
export const resolvers = {
Query:{
async getPosts(_,{ limit },context){
const post = Post.find()
console.log(limit)
return(post)
}}
}
Mutation:{...}
I do not understand at all how to limit the getPosts query. With Posts.find() i get all the items in my website but when i console.log it it gives me a huge chunk of metadata.
Could anybody please give an example of how to filter for lets say first 10 objects based on my situation. The data that i am filtering is in mongo db. so basically a standard json like :
{
"data": {
"getPosts": [
{
"word": "test"
},
{
"word": "test1"
}
]
}
}
It's not related to grapql at all! You have to slice the array of documents you are getting from your your mongoose model. In your case the getPosts function will return an array of Post documents. So, you just have to slice & return the array.
async getPosts(_,{ limit },context){
const post = Post.find()
return post.slice(0, limit)
}}
Or even better, you could use mongoose limits:
async getPosts(_,{ limit },context){
const post = Post.find().limit(limit)
return post
}}
That's it 😉.
I am trying to figure out how to authenticate my request to firestore.
I am using https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=<API_KEY> for both which returns me an idToken of the user.
I do authentication on my routes which work but exposes my firestore still, thus me switching over to using security rules but I can't seem to authenticate any request.
I am using express to handle routes to firestore using this format locally:
GET http://localhost:5001/<PROJECT_ID>/us-central1/database/users/
Rules: allow read, write: if true;
GET https://firestore.googleapis.com/v1/projects/<PROJECT_ID>/databases/(default)/documents/users
content-type: application/json
Response: 200 (And I see all the documents)
Rules: allow read, write: if request.auth != null;
GET https://firestore.googleapis.com/v1/projects/<PROJECT_ID>/databases/(default)/documents/users
Authorization: Bearer {{idToken}}
content-type: application/json
Response: {
"error": {
"code": 403,
"message": "Missing or insufficient permissions.",
"status": "PERMISSION_DENIED"
}
}
MORE DETAILED INFO
The code below works, but with firebase other way of getting the data, it bypasses this and will only limit based on security rules.
index.ts
import * as functions from 'firebase-functions';
import * as express from 'express';
import * as cors from 'cors';
import isAuthenticated from './components/middleware/authenticated';
import isAuthorized from './components/middleware/authorized';
import users_all from './users/controllers/all';
const route = express();
route.use(cors({ origin: true }));
route.get('/users', isAuthenticated, isAuthorized({ hasRole: ['admin', 'manager'] }), users_all);
exports.database = functions.https.onRequest(route);
users_all
import { Request, Response } from "express";
import sentry from '../../components/reporting/sentry';
import Fireapp from '../../components/firebase/fireapp';
Fireapp
const all = async (req: Request, res: Response) => {
try {
let profiles: any = [];
/** Retrieve the exact document reference */
const reference = Fireapp.firestore().collection('users').get()
.then((documents: firebase.firestore.QuerySnapshot) => {
documents.docs.forEach((doc: firebase.firestore.DocumentData) => { profiles.push(doc.data()) });
return profiles;
});
return Promise.all([reference]).then((response: any) => {
res.status(200).send(profiles);
}).catch((error: any) => { throw error });
} catch (error) {
sentry(error, { service: '[GET ALL USER]', level: 'moderate', message: error.message });
res.status(400).send(error.message)
}
}
export default all;