I have been trying to figure out how to make the switcher or language selector work from the frontend part of the website. Also my first time working with SSR, so I am not sure about the communication in between. So far what I handled and works well, the inclusion for the translation rendering is working, also when i manually change the language for testing purpose.
I have been reading the documentation, but there is not specific tutorial about how to handle this, more than the function "changeLanguage" and some solutions I found are older as 5 years and involves, deprecated version and some kind of extra library to handled some template views, and I don't know if if this actually is still feasible. However inside the repository from i18next-http-middleware, there is an example that basically does kind what I want. So can be found under the "basic-pug" folder. The difference is that it is used "Pug" as a template engine, which I don't think it should affect or make any difference. so I used exactly the same configuration as the example, which i downloaded and tested and it was working, so it supposed enabled a cookie, and then on the url can pass the cookie as a param, with the locale, and it supposed to work, however not working with in my project, and I also wondering if it possible to have "/de/" instead "/?lang=de", example www.myweb.com/de/about and not www.myweb.com/about/?lang=de, because that's not exactly what I was imagining, and also no clear how to handled when u have nested or several routes.
In general, I was wondering how to use the "changelanguage" function in the frontend side, so I have, for example, something like:
<div>
<div onclick="Changelanguage("de")"> Deutsch</div>
<div onclick="Changelanguage("en")"> English</div>
</div>
Because by declaring the function just like in the views, on a script tag, I the changelanguage is not a function, because there is no communication between both.
Please find below my configuration:
Json file:
{
"name": "project",
"version": "0.0.0",
"private": true,
"author": "project",
"main": "dist/index.js",
"scripts": {
"start": "node dist/index.js",
"dev": "nodemon src/index.js --exec \"node -r dotenv/config -r #babel/register\"",
"clean": "rimraf dist",
"build": "npm run clean && mkdir -p dist && babel src -s -D -d dist",
"postinstall": "npm run build"
},
"dependencies": {
"#babel/cli": "^7.18.9",
"#babel/core": "^7.18.9",
"#babel/plugin-proposal-class-properties": "^7.18.6",
"#babel/plugin-proposal-object-rest-spread": "^7.18.9",
"#babel/preset-env": "^7.18.9",
"body-parser": "^1.19.0",
"ejs": "^3.1.8",
"express": "^4.18.1",
"graphql": "^16.5.0",
"graphql-request": "^4.3.0",
"i18next": "^21.9.1",
"i18next-fs-backend": "^1.1.5",
"i18next-http-middleware": "^3.2.1",
"locomotive-scroll": "^4.1.4",
"morgan": "^1.10.0",
"node-sass-middleware": "^1.0.1",
"rimraf": "^3.0.0"
},
"devDependencies": {
"#babel/register": "^7.18.9",
"dotenv": "^16.0.1",
"nodemon": "^2.0.19"
},
"babel": {
"presets": [
[
"#babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
],
"plugins": [
"#babel/plugin-proposal-class-properties",
"#babel/plugin-proposal-object-rest-spread"
]
}
}
My routes file js
import { GraphQLClient
} from "graphql-request";
import { Router
} from "express";
import * as queries from "./queries";
import i18next from "i18next";
const routes = Router();
export function request({ query, variables, preview, includeDrafts
}) {
// ...some headers and client configs
};
return client.request(query, variables);
}
let generalInfo = null;
const getData = async (options) => {
options.page = options.page ? options.page : "";
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: options.lang
}
});
}
let q = queries.getPage(options.page);
let data = await request({
query: q,
variables: {
locale: options.lang
}
});
data = Object.assign(data, generalInfo);
return data;
};
const getCase = async (slug, lang) => {
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: lang
}
});
}
slug = slug ? slug : "";
let q = queries.getCase;
let data = await request({
query: q,
variables: {
locale: lang
}
});
data = Object.assign(data, generalInfo);
const filter = data.allCases.filter((n) => n.slug == slug)
return filter;
};
const getInnerPage = async (slug, lang) => {
if (!generalInfo) {
generalInfo = await request({
query: queries.getGeneralInfo,
variables: {
locale: lang
}
});
}
slug = slug ? slug : "";
let q = queries.getInner;
let data = await request({
query: q,
variables: {
locale: lang
}
});
data = Object.assign(data, generalInfo);
const filter = data.allPages.filter((n) => n.slug == slug);
return filter;
};
routes.get("/cases/:slug", (req, res) => {
let slug = req.params.slug;
getCase(slug, req.locale).then(filter => {
if (filter.length) {
res.render("case",
{ title: filter.project_title, filter, t: i18next.t
});
} else {
res.render("404");
}
}).catch(e => {
console.error(e);
});
});
routes.get("/page/:slug", (req, res) => {
let slug = req.params.slug;
getInnerPage(slug, req.locale).then(filter => {
if (filter.length) {
res.render("page",
{ title: filter[
0
].titlePage, filter, t: i18next.t
});
} else {
res.render("404");
}
}).catch(e => {
console.error(e);
});
});
routes.get("/", (req, res) => {
let options = { 'lang': req.locale, 'page': ""
}
//console.log(req.locale + " locale selected");
getData(options).then(data => {
res.render("home",
{ title: "Home", data, t: i18next.t
});
}).catch(e => {
console.error(e);
});
});
export default routes;
My index js
import express from "express";
import path from "path";
import logger from "morgan";
import bodyParser from "body-parser";
import routes from "./routes";
import sassMiddleware from "node-sass-middleware";
import i18next from "i18next";
import i18nextBackend from "i18next-fs-backend";
import i18nextMiddleware from "i18next-http-middleware";
const { PORT = 5050 } = process.env;
const app = express();
i18next.use(i18nextBackend)
.use(i18nextMiddleware.LanguageDetector)
.init({
//lng: 'de',
debug: true,
fallbackLng: 'en',
preload: ['de', 'en'],
backend: {
loadPath: './locales/{{lng}}/translation.json'
},
detection: {
order: ['querystring', 'cookie'],
caches: ['cookie'],
lookupQuerystring: 'lang',
lookupCookie: 'lang',
ignoreCase: true,
cookieSecure: false
},
})
app.use(i18nextMiddleware.handle(i18next));
//i18next.changeLanguage('de');
app.set("views", path.join(__dirname, "../views"));
app.set("view engine", "ejs");
app.use(logger("dev"));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.locals = { config: { whatever: 'this is' } };
app.use(
sassMiddleware({
src: path.join(__dirname, "../public/scss"),
dest: path.join(__dirname, "../public"),
indentedSyntax: false,
sourceMap: false
})
);
app.use(express.static(path.join(__dirname, "../public")));
app.use("/", routes);
app.use(function (req, res) {
res.status(404).render('404.ejs');
});
app.use(function (req, res) {
res.status(500).render('500.ejs');
});
app.listen(PORT, () => {
console.log(`Listening on port ${PORT}`);
});
export default app;
Thank you in advance for any help, I think I am mainly struggling because I use to work on client side, but still learning about SSR.
You should be able to define the language also in the route, like described here: https://github.com/i18next/i18next-http-middleware#language-detection
Define the 'path' in the detection options: order: ['path', /*'session', */ 'querystring', 'cookie', 'header'],
Regarding the language change. You probably just need to navigate to the route with the appropriate language.
Related
I don't understand these errors when I export as production npm run build , but when I test npm run dev it works just fine. I use getStaticProps and getStaticPath fetch from an API route.
First when I npm run build
FetchError: invalid json response body at https://main-website-next.vercel.app/api/products reason: Unexpected token T in JSON at position
0
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:272:32
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\product\[slug].js:1324:18)
at async buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:80)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:612
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'invalid-json'
}
\pages\product\[slug]
import { assetPrefix } from '../../next.config'
export default function Page(){...}
export const getStaticProps = async ({ params: { slug }, locale }) => {
const res = await fetch(`${assetPrefix}/api/products/${slug}`)
const result = await res.json()
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const res = await fetch(`${assetPrefix}/api/products`)
const result = await res.json()
const paths = result.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
next.config.js
const isProd = process.env.NODE_ENV === 'production'
module.exports = {
assetPrefix: isProd ? 'https://main-website-next.vercel.app' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
API routes
// pages/api/products/index.js
import data from '../../../data/products'
export default (req, res) => {
res.status(200).json(data)
}
// pages/api/products/[slug].js
import db from '../../../data/products'
export default ({ query: { slug } }, res) => {
const data = db.filter(item => item.slug === slug)
if (data.length > 0) {
res.status(200).json(data)
} else {
res.status(404).json({ message: `${slug} not found` })
}
}
// ../../../data/products (data source)
module.exports = [
{ locale: "en", slug: "google-sheets-combine-your-cashflow",
title: "Combine your cashflow",
keywords: ["Google Sheets","accounting"],
description: "...",
},
...
]
Second when I remove the production domain, I run npm run build but still get the error like
TypeError: Only absolute URLs are supported
at getNodeRequestOptions (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1305:9)
at D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1410:19
at new Promise (<anonymous>)
at fetch (D:\zummon\Main Website\main-website-next\node_modules\node-fetch\lib\index.js:1407:9)
at getStaticPaths (D:\zummon\Main Website\main-website-next\.next\server\pages\[slug].js:938:21)
at buildStaticPaths (D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:16:86)
at D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\utils.js:26:618
at processTicksAndRejections (internal/process/task_queues.js:97:5)
at async D:\zummon\Main Website\main-website-next\node_modules\next\dist\build\tracer.js:1:1441 {
type: 'TypeError'
}
My next.config.js after remove
const isProd = process.env.NODE_ENV === 'production'
module.exports = { //remove
assetPrefix: isProd ? '' : 'http://localhost:3000',
i18n: {
localeDetection: false,
locales: ['en', 'th'],
defaultLocale: 'en',
}
}
My package.json when I npm run build script
{
"name": "main-website-next",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build && next export",
"start": "next start"
},
"dependencies": {
"next": "10.0.6",
"react": "17.0.1",
"react-dom": "17.0.1"
}
}
You should not call an internal API route inside getStaticProps. Instead, you can safely use your API logic directly in getStaticProps/getStaticPaths. These only happen server-side so you can write server-side code directly.
As getStaticProps runs only on the server-side, it will never run on
the client-side. It won’t even be included in the JS bundle for the
browser, so you can write direct database queries without them being
sent to browsers.
This means that instead of fetching an API route from
getStaticProps (that itself fetches data from an external source),
you can write the server-side code directly in getStaticProps.
Furthermore, your API routes are not available during build-time, as the server has not been started at that point.
Here's a small refactor of your code to address the issue.
// /pages/product/[slug]
import db from '../../../data/products'
// Remaining code..
export const getStaticProps = async ({ params: { slug }, locale }) => {
const result = db.filter(item => item.slug === slug)
const data = result.filter(item => item.locale === locale)[0]
const { title, keywords, description } = data
return {
props: {
data,
description,
keywords,
title
}
}
}
export const getStaticPaths = async () => {
const paths = db.map(({ slug, locale }) => ({ params: { slug: slug }, locale }))
return {
fallback: true,
paths,
}
}
I am building a project and want to store the session's data in MongoDB with the help of ApolloServer, Express, GraphQL
However, I am unable to set the cookie on the browser (Chrome & Firefox)
Initially, I had a few CORS errors, I believe I fixed them
The session data and ID are being stored in MongoDB successfully
When I log in and run the "me" query to check if the cookie was set, it returns null as in nothing was set
Please help me
index.js
import "reflect-metadata";
import { MikroORM } from "#mikro-orm/core";
import { ApolloServer } from "apollo-server-express";
import express from "express";
import { buildSchema } from "type-graphql";
import mikroOrmConfig from "./mikro-orm.config";
import { HelloResolver } from "./resolvers/hello";
import { PostResolver } from "./resolvers/post";
import { UserResolver } from "./resolvers/user";
import session from "express-session";
import MongoStore from "connect-mongo";
import { __prod__ } from "./constants";
import { MyContext } from "./types";
const main = async () => {
const orm = await MikroORM.init(mikroOrmConfig);
await orm.getMigrator().up();
const app = express();
const corsOptions = {
origin: 'https://studio.apollographql.com',
credentials: true
}
app.set("trust proxy", 1);
app.use(
session({
name: "qid",
secret: "secret",
saveUninitialized: false,
resave: false,
store: MongoStore.create({
mongoUrl: "mongodb://localhost/test-app",
touchAfter: 24 * 3600, // time period in seconds
}),
cookie: {
maxAge: 3600 * 60 *30 * 24 * 3600,
secure: true,
httpOnly: true,
sameSite: 'lax'
}
})
);
const apolloServer = new ApolloServer({
schema: await buildSchema({
resolvers: [HelloResolver, PostResolver, UserResolver],
validate: false,
}),
context: ({ req, res }): MyContext => ({ em: orm.em.fork(), req, res }),
});
await apolloServer.start();
apolloServer.applyMiddleware({ app, cors: corsOptions });
app.listen(4000, () => {
console.log("server started on port 4000");
});
};
package.json
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"#types/connect-mongo": "^3.1.3",
"#types/express": "^4.17.13",
"#types/express-session": "^1.17.4",
"#types/mongodb": "^4.0.7",
"#types/node": "^16.7.2",
"express-session": "^1.17.2",
"nodemon": "^2.0.12",
"ts-node": "^10.2.1",
"typescript": "^4.3.5"
},
"dependencies": {
"#mikro-orm/cli": "^4.5.9",
"#mikro-orm/core": "^4.5.9",
"#mikro-orm/migrations": "^4.5.9",
"#mikro-orm/postgresql": "^4.5.9",
"apollo-server-express": "^3.3.0",
"argon2": "^0.28.2",
"connect-mongo": "^4.5.0",
"express": "^4.17.1",
"graphql": "^15.5.1",
"pg": "^8.7.1",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.1.1"
},
"mikro-orm": {
"configPaths": [
"./sr/mikro-orm.config.ts",
"./dist/mikro-orm.config.js"
]
}
user.ts login Mutation
#Mutation(() => UserResponse)
async login(
#Arg("options", () => UsernamePasswordInput) options: UsernamePasswordInput,
#Ctx() { em, req }: MyContext
): Promise<UserResponse> {
const user = await em.findOne(User, { username: options.username });
if (!user) {
return {
errors: [
{
field: "username",
message: "Invalid Username",
},
],
};
}
const valid = await argon2.verify(user.password, options.password);
if (!valid) {
return {
errors: [
{
field: "password",
message: "Invalid Password",
},
],
};
}
req.session.userId = user.id;
return {
user,
};
}
user.ts me Query
#Query(() => User, {nullable: true})
async me(
#Ctx() { req, em }: MyContext,
){
if(!req.session.userId){
return null;
}
else {
const user = await em.findOne(User, { id: req.session.userId });
return user;
}
}
The context
import { EntityManager, IDatabaseDriver, Connection } from "#mikro-orm/core";
import { Request, Response } from "express";
import { Session } from 'express-session';
export interface MyContext {
em: EntityManager<any> & EntityManager<IDatabaseDriver<Connection>>;
req: Request & { session?: Session & { userId?: number }};
res: Response;
};
Session data getting stored in MongoDB
ApolloGraphQL, the user is null even after login
Please help me or give me some pointers if I have made any mistakes, Thank You!
I am trying to implement avatar upload in Next.js blog with Node.js server using Apollo client + apollo-upload-client on client side and apollo-express-server on server side.
I've got the next error:
POST body missing. Did you forget use body-parser middleware?
I am sure that I have body parser on my server.
Server.ts
import "reflect-metadata";
import "dotenv-safe/config";
import 'module-alias/register';
import { __prod__ } from "#/config/config";
import express from "express";
import Redis from "ioredis";
import session from "express-session";
import connectRedis from "connect-redis";
import { createConnection } from "typeorm";
import { User } from "#/entities/User";
import { Project } from "#/entities/Project";
import path from "path";
const server = async () => {
await createConnection({
type: "postgres",
url: process.env.DATABASE_URL,
logging: true,
migrations: [path.join(__dirname, "./migrations/*")],
entities: [User, Project]
});
const app = express();
require("#/start/logger"); // log exceptions
const RedisStore = connectRedis(session); // connect redis store
const redis = new Redis(process.env.REDIS_URL);
require("#/start/apolloServer")(app, redis); // create apollo server
require("#/start/appConfig")(app,redis,RedisStore) // configure app
const PORT = process.env.PORT || 3007;
app.listen(PORT, () => {
console.log(`🚀 Server Started at PORT: ${PORT}`);
});
};
server().catch((err) => {
console.error(err);
});
My Apollo Server
I use apollo-server-express
import { ApolloServer, gql } from "apollo-server-express";
import { buildSchema } from "type-graphql";
import ProfilePictureResolver from "#/resolvers/upload";
import { createUserLoader } from "#/utils/createUserLoader";
import { UserResolver } from "#/resolvers/user";
import { ProjectResolver } from "#/resolvers/project";
import {Express} from "express";
import { Redis } from "ioredis";
const typeDefs = gql`
scalar Upload
type File {
id: ID!
filename: String!
mimetype: String!
path: String!
}
type Mutation {
singleUpload(file: Upload!): File!
}
`;
module.exports = async function(app:Express,redis:Redis){
const apolloServer = new ApolloServer({
typeDefs,
schema: await buildSchema({
resolvers: [UserResolver, ProjectResolver, ProfilePictureResolver],
validate: false,
}),
context: ({ req, res }) => ({
req,
res,
redis,
userLoader: createUserLoader()
}),
uploads: false
});
apolloServer.applyMiddleware({
app,
cors: false,
});
}
Resolver:
import { Resolver, Mutation, Arg } from 'type-graphql'
import { GraphQLUpload, FileUpload } from 'graphql-upload'
import os from 'os'
import { createWriteStream } from 'fs'
import path from 'path'
#Resolver()
export default class SharedResolver {
#Mutation(() => Boolean)
async uploadImage(
#Arg('file', () => GraphQLUpload)
file: FileUpload
): Promise<Boolean> {
const { createReadStream, filename } = await file
const destinationPath = path.join(os.tmpdir(), filename)
const url = await new Promise((res, rej) =>
createReadStream()
.pipe(createWriteStream(destinationPath))
.on('error', rej)
.on('finish', () => {
//stuff to do
})
);
return true;
}
}
Server config
import {Express} from 'express'
import { __prod__, COOKIE_NAME } from "#/config/config";
import cors from "cors";
import session from "express-session";
import { Redis } from 'ioredis';
import { RedisStore } from 'connect-redis';
import { bodyParserGraphQL } from 'body-parser-graphql'
module.exports = function(app:Express, redis:Redis, RedisStore:RedisStore){
app.set("trust proxy", 1);
app.use(bodyParserGraphQL());
app.use(
cors({
origin: process.env.CORS_ORIGIN,
credentials: true,
})
);
app.use(
session({
name: COOKIE_NAME,
store: new RedisStore({
client: redis,
disableTouch: true,
}),
cookie: {
maxAge: 1000 * 60 * 60 * 24 * 365 * 10, // 10 years
httpOnly: true,
sameSite: "lax",
secure: __prod__,
domain: __prod__ ? ".heroku.com" : undefined,
},
saveUninitialized: false,
secret: process.env.SESSION_SECRET,
resave: false,
})
);
}
Client
App client
import { createWithApollo } from "#/utils/createWithApollo";
import { ApolloClient, InMemoryCache } from "#apollo/client";
import { NextPageContext } from "next";
import { createUploadLink } from 'apollo-upload-client';
const createClient = (ctx: NextPageContext) =>
new ApolloClient({
credentials: "include",
headers: {
cookie:
(typeof window === "undefined"
? ctx?.req?.headers.cookie
: undefined) || "",
},
cache: new InMemoryCache({
typePolicies: {
Query: {}
}
}),
link: createUploadLink({uri:'http://localhost:4000/graphql'})
});
// const createClient: ApolloClient<NormalizedCacheObject> = new ApolloClient({
// cache: new InMemoryCache({}),
// uri: 'http://localhost:4000/graphql'
// });
export const withApollo = createWithApollo(createClient);
Query
import { gql } from '#apollo/client';
export const UPLOAD_IMAGE_MUTATION = gql`
mutation uploadImage($file: Upload!) {
uploadImage(file: $file)
}
`;
Page
import React, {useState} from 'react';
import {useSelector} from "react-redux";
import {Box} from "#/components/UI/Box/Box"
import {Header} from "#/components/UI/Text/Header"
import { withApollo } from "#/utils/withApollo";
import withPrivateRoute from "#/HOC/withPrivateRoute";
import { useMutation } from "#apollo/react-hooks";
import { UPLOAD_IMAGE_MUTATION } from "#/graphql/mutations/uploadImage";
interface IProps{};
const Profile:React.FC<IProps> = () => {
const user = useSelector(state => state.user);
const [file, setFileToUpload] = useState(null);
const [uploadImage, {loading}] = useMutation(UPLOAD_IMAGE_MUTATION);
const onAvatarUpload = (e) =>{
setFileToUpload(e.target.files[0]);
}
const onSubmit = async (e) =>{
e.preventDefault();
const response = await uploadImage({
variables: {file}
});
}
return (
<Box mt={20} pl={30} pr={30}>
<Header>
Edit Profile
</Header>
<input onChange={onAvatarUpload} type="file" placeholder="photo" />
<button onClick={(e)=>onSubmit(e)}>Submit</button>
</Box>
)
};
export default withApollo({ ssr: false })(withPrivateRoute(Profile, true));
My Client package:
{
"name": "app",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"#apollo/client": "^3.2.5",
"#apollo/react-hooks": "^4.0.0",
"apollo-upload-client": "^14.1.3",
"graphql": "^15.4.0",
"graphql-tag": "^2.11.0",
"graphql-upload": "^11.0.0",
"isomorphic-unfetch": "^3.1.0",
"next": "^9.5.5",
"next-apollo": "^5.0.3",
"next-redux-wrapper": "^6.0.2",
"react": "^16.14.0",
"react-dom": "^16.14.0",
"react-is": "^16.13.1",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"styled-components": "^5.2.1",
"urql": "^1.10.3",
"uuid": "^8.3.1"
},
"devDependencies": {
"#testing-library/jest-dom": "^5.11.5",
"#testing-library/react": "^11.1.1",
"#types/graphql": "^14.5.0",
"#types/jest": "^26.0.15",
"#types/next": "^9.0.0",
"#types/node": "^14.0.27",
"#types/react": "^16.9.55",
"#types/react-dom": "^16.9.9",
"#types/styled-components": "^5.1.4",
"#types/uniqid": "^5.2.0",
"#types/uuid": "^8.3.0",
"#welldone-software/why-did-you-render": "^5.0.0",
"babel-plugin-inline-react-svg": "^1.1.2",
"babel-plugin-module-resolver": "^4.0.0",
"babel-plugin-styled-components": "^1.11.1",
"redux-devtools-extension": "^2.13.8",
"typescript": "^4.0.5"
}
}
Server package:
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "server.ts",
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"nodemon": "nodemon dist/server.js",
"dev": "npm-run-all --parallel watch nodemon",
"start": "ts-node src/server.ts",
"client": "cd ../ && npm run dev --prefix client",
"runall": "npm-run-all --parallel client dev",
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js",
"migration:up": "typeorm migration:run",
"migration:down": "typeorm migration:revert",
"migration:generate": "typeorm migration:generate -n 'orm_migrations'"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"apollo-server-express": "^2.16.1",
"argon2": "^0.26.2",
"connect-redis": "^5.0.0",
"cors": "^2.8.5",
"dataloader": "^2.0.0",
"dotenv-safe": "^8.2.0",
"express": "^4.17.1",
"express-async-errors": "^3.1.1",
"express-session": "^1.17.1",
"graphql": "^15.3.0",
"ioredis": "^4.17.3",
"module-alias": "^2.2.2",
"path": "^0.12.7",
"pgtools": "^0.3.0",
"reflect-metadata": "^0.1.13",
"type-graphql": "^1.0.0-rc.3",
"typeorm": "^0.2.25",
"uuid": "^8.3.0",
"winston": "^3.3.3"
},
"devDependencies": {
"#types/connect-redis": "0.0.14",
"#types/cors": "^2.8.8",
"#types/express": "^4.17.8",
"#types/express-session": "^1.17.0",
"#types/graphql": "^14.5.0",
"#types/ioredis": "^4.17.7",
"#types/node": "^8.10.66",
"#types/nodemailer": "^6.4.0",
"#types/pg": "^7.14.6",
"#types/uuid": "^8.3.0",
"gen-env-types": "^1.0.4",
"nodemon": "^2.0.6",
"npm-run-all": "^4.1.5",
"pg": "^8.4.2",
"ts-node": "^8.10.2",
"typescript": "^3.9.7"
},
"_moduleAliases": {
"#": "dist/"
}
}
NOTE!
When I try to remove uploads: false from apolloServer configuration I receive another error:
"Variable "$file" got invalid value {}; Upload value invalid."
And indeed in form data I see
------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="operations"
{"operationName":"uploadImage","variables":{"file":null},"query":"mutation
uploadImage($file: Upload!) {\n uploadImage(file: $file)\n}\n"}
------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="map"
{"1":["variables.file"]}
------WebKitFormBoundarybNufV7QLX3EU1SN6 Content-Disposition: form-data; name="1"; filename="Screen Shot 2020-11-20 at 17.56.14.png"
Content-Type: image/png
------WebKitFormBoundarybNufV7QLX3EU1SN6--
I am 100% sure that I pass the file.
I faced the same problem in my NextJs project, I found that the resolver of Upload checks if the value is instanceOf Upload, and that is somehow not working.
I fix it by creating my own resolver without using the 'graphql-upload' package like this:
Solution 1 :
export const resolvers: Resolvers = {
Upload: new GraphQLScalarType({
name: 'Upload',
description: 'The `Upload` scalar type represents a file upload.',
parseValue(value) {
return value;
},
parseLiteral(ast) {
throw new GraphQLError('Upload literal unsupported.', ast);
},
serialize() {
throw new GraphQLError('Upload serialization unsupported.');
},
})
};
Solution 2 :
Or you can just don't declare any resolver for this type.
Note:
Be sure that you declared scalar type of Upload in your schema and you need to add the uploads field to your Apollo Server configuration:
const apolloServer = new ApolloServer({
uploads: {
maxFileSize: 10000000, // 10 MB
maxFiles: 20
},
.
.
.
I'm trying to mock out RNCamera in a Detox test. However, the mock camera does not override the real implementation.
I followed these instructions and incorporated advice from these posts.
The result is:
The mock camera class file itself is executed (top-level logging is printed)
Mock camera's takePictureAsync and render functions are not called
Emulator's default camera is used
I have also tried:
RN_SRC_EXT=e2e react-native run-android, then detox test
adding "test-runner": "jest" and callingjest.mock` in the best body
replacing RN_SRC_EXT=e2e with RN_SRC_EXT=e2e.js
cleanup: rm -rf node_modules; yarn install etc.
My app layout is like (non-test-relevant content excluded):
package.json:
{
...
"dependencies": {
...
"#types/jest": "^24.0.21",
"react": "16.9.0",
"react-dom": "latest",
"react-native": "0.61.1",
"react-native-camera": "^3.6.0",
},
"devDependencies": {
"#babel/core": "latest",
"#babel/preset-env": "latest",
"#babel/register": "latest",
"#babel/preset-react": "latest",
"#react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"detox": "^14.5.1",
"jest": "^24.9.0",
"jest-fetch-mock": "^2.1.2",
"metro-react-native-babel-preset": "^0.56.0",
"mocha": "^6.2.2",
"react-test-renderer": "16.9.0"
},
"detox": {
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
},
"android.emu.release": {
"binaryPath": "android/app/build/outputs/apk/release/app-release.apk",
"build": "RN_SRC_EXT=e2e cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release && cd ..",
"type": "android.emulator",
"device": {
"avdName": "Pixel_2_API_29"
}
}
}
}
}
e2e/config.js:
require('#babel/register')({
//cache: true,
presets: [require('metro-react-native-babel-preset')],
plugins: [require('#babel/plugin-transform-runtime').default],
only: ['./e2e', './js'],
ignore: ['node_modules']
});
e2e/config.json:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
e2e/mocha.opts:
--recursive
--timeout 300000
--bail
--file e2e/init.js
--require e2e/config.js
--require e2e/config.json
--file e2e/react-native-camera.e2e.js
metro.config.js:
const defaultSourceExts = require('metro-config/src/defaults/defaults').sourceExts
module.exports = {
transformer: {
getTransformOptions: async () => ({
transform: {
experimentalImportSupport: false,
inlineRequires: false,
},
}),
},
resolver: {
sourceExts: process.env.RN_SRC_EXT
? process.env.RN_SRC_EXT.split(',').concat(defaultSourceExts)
: defaultSourceExts
}
};
// printed as: module.exports.resolver from e2e { sourceExts: [ 'e2e', 'js', 'json', 'ts', 'tsx' ] }
console.log("module.exports from e2e", module.exports);
e2e/config.json:
{
"setupFilesAfterEnv": ["./init.js"],
"testEnvironment": "node",
"reporters": ["detox/runners/jest/streamlineReporter"],
// tried with and without this line
"testMatch": ["**/__tests__/**/*.js?(x)", "**/?(*.)(e2e).js?(x)"],
"verbose": true
}
test.spec.js
describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
});
it('should blah blah balh', async () => {
// test implementation
});
});
init.js:
const detox = require('detox');
const config = require('../package.json').detox;
const adapter = require('detox/runners/mocha/adapter');
before(async () => {
await detox.init(config);
});
beforeEach(async function() {
await adapter.beforeEach(this);
});
afterEach(async function() {
await adapter.afterEach(this);
});
after(async () => {
await detox.cleanup();
});
e2e/react-native-camera.e2e.js: (from here)
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
// This IS printed on detox test -c android.emu.debug.
console.log("executing react-native-camera-e2e.js");
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
};
takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // Never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
// This is never printed.
console.log("mock RNCamera render()");
return null;
}
}
export const takePictureAsync = async () => {
console.log("mock RNCamera takePictureAsync"); // never printed
await timeout(2000);
return { uri: './static-image.jpg' };
};
export default RNCamera;
Mocking with detox and mocking with Jest are 2 different things.
In order to be able to mock third party module with detox, you can create a proxy component provider that will decide what component to use. So that you will be able to load the mocked version for detox testing.
In your case you can create a CameraProvider.js that will just export the real RNCamera:
export { RNCamera } from 'react-native-camera';
Then you will need to create the mocked version CameraProvider.e2e.js alongside the previous file for end-to-end testing with detox:
import React from 'react';
const timeout = ms => new Promise(resolve => setTimeout(resolve, ms));
export class RNCamera extends React.Component {
static Constants = {
Aspect: {},
BarCodeType: {},
Type: { back: 'back', front: 'front' },
CaptureMode: {},
CaptureTarget: {},
CaptureQuality: {},
Orientation: {},
FlashMode: {},
TorchMode: {},
AutoFocus: { on: {} },
WhiteBalance: { auto: {} },
};
takePictureAsync = async () => {
console.log('mock RNCamera takePictureAsync');
await timeout(2000);
return { uri: './static-image.jpg' };
};
render() {
console.log('mock RNCamera render()');
return null;
}
}
Finally, if you have correctly configured your metro bundler in the root folder of your react-native project, you should be able to either build your app or start the bundler (for debug targets) with the RN_SRC_EXT=e2e.js env variable to let the metro bundler know which file extensions it should replace.
Gomino's answer is correct but its missing that you will need to import from the proxy component in the code under test.
E.G. Do this in your component:
import { RNCamera } from '../proxies/RNCamera';
And not
import { RNCamera } from 'react-native-camera';
Then you need to make sure the bundler is started with the RN_SRC_EXT=e2e.js env variable. So my detox test scripts (in package.json) look something like this:
"start-detox-metro": "RN_SRC_EXT=e2e.js react-native start",
"detox-build": "detox build --configuration android.emu.debug",
"detox test": "detox test --configuration android.emu.debug --reuse",
Run the scripts in that order.
The metro bundler will handle swapping the .e2e.js files in place of the original proxy file (but only when the env var "RN_SRC_EXT=e2e.js" is set).
So I've tried both the Nunjucks example in the Vision docs as well as the plugin nunjucks-hapi with the same result - watch does not work.
var viewPath = Path.join(__dirname, 'views')
var env = NunjucksHapi.configure(viewPath, { watch: true})
server.register(plugins, (err) => {
if (err) throw err;
server.views({
engines: {
html: NunjucksHapi
},
path: viewPath
});
server.route(...);
server.start((err) => {
});
});
Packages:
"hapi": "^13.0.0",
"nunjucks": "^2.3.0",
"nunjucks-hapi": "^2.0.1",
"vision": "^4.0.1"
Suggestions as to where to look for issue?
try it:
server.register(require('vision'), (err) => {
Hoek.assert(!err, err)
server.views({
engines: {
njk: require('nunjucks-hapi')
},
relativeTo: __dirname,
path: 'resources/views'
})
})