I'm trying to create a simple graphql backend using apollo-server-express.
Doing this in a expressJS application (Github repo) is working as expected:
Executing the query
query {
search {
result
}
}
at localhost:4000/graphql returns result as expected.
resolver
module.exports = {
Query: {
search: async (obj, { name, value }) => {
return 'result'
}
}
}
Now I would like to use this expressJS backend in a nx.dev environment (Github repo). I set up the same thing here, but running the server and executing the query in the graphQL playground returns null.
I don't see what is going wrong.
This is my structure:
main.ts
const express = require('express');
const { ApolloServer } = require('apollo-server-express');
const typeDefs = require('./graphql/types')
const resolvers = require('./graphql/resolvers')
const app = express();
const server = new ApolloServer({ typeDefs, resolvers });
server.applyMiddleware({ app });
app.listen({ port: 4000 }, () =>
console.log(`π Server ready at http://localhost:4000${server.graphqlPath}`)
)
structure
+-- graphql
| +-- author
| | +-- author.resolvers.js
| | +-- author.graphql
| +-- search
| | +-- search.resolvers.js
| | +-- search.graphql
| +-- resolvers.js <<< Merges all `*.resolvers.*` files
| +-- types.js <<< Merges all `*.graphql` files
resolvers.js
const path = require('path');
const { mergeResolvers } = require('#graphql-tools/merge');
const { loadFilesSync } = require('#graphql-tools/load-files');
const resolversArray = loadFilesSync(path.join(__dirname, "./**/*.resolvers.*"));
module.exports = mergeResolvers(resolversArray);
types.js
const path = require('path');
const { loadFilesSync } = require('#graphql-tools/load-files');
const { mergeTypeDefs } = require('#graphql-tools/merge');
const typesArray = loadFilesSync(path.join(__dirname, './**/*.graphql'))
module.exports = mergeTypeDefs(typesArray, { all: true })
graphql/search/search.graphql
type Query {
search(name: String, value: String): [String]
}
graphql/search/search.resolvers.js
module.exports = {
Query: {
search: async (obj, { name, value }) => {
return 'result'
}
}
}
Related
I am creating a custom middleware for strapi that will change the response body to the hash.
I need to use this middleware for all routes in strapi, means on the application level.
'use strict';
/**
* `ctx-decrypt` middleware.
*/
var crypto = require('crypto');
const algorithm = 'aes-256-cbc';
function decrypt(text,key)
{
let iv = Buffer.from(text.iv, 'hex');
let encryptedText = Buffer.from(text.encryptedData, 'hex');
let decipher = crypto.createDecipheriv('aes-256-cbc', Buffer.from(key), iv);
let decrypted = decipher.update(encryptedText);
decrypted = Buffer.concat([decrypted, decipher.final()]);
return decrypted.toString();
}
function hash(data)
{
var hash = crypto.createHash('sha256', data);
return hash;
}
module.exports = (config,{env},{strapi}) =>
{
return
{
initialize()
{
strapi.app.use(async (ctx, next) =>
{
strapi.log.info('In ctx-decrypt middleware.');
var ENC_KEY = process.env.ENC_KEY;
var requestBody = ctx.request.body;
//var responseBody = ctx.body;
var dc = decrypt(requestBody,ENC_KEY);
if(!dc)
{
throw new Error('Invalid request body!');
ctx.response.set(hash("5u5234803riqifn"));
console.log(ctx.body)
await next();
}
else
{
ctx.response.set(hash("5u5234803riqifn"));
console.log(ctx.body)
await next();
}
});
}
};
};
Now I followed their latest docs but strapi always give some errors on using that middleware.
I ran
npx strapi generate
to generate middleware at the root of project.
and then I ran
npx strapi middlewares:list
to get all the middleware names, then added my 'global::ctx-decrypt' middleware name in the ./config/middlewares.js like this
module.exports = [
'strapi::errors',
'strapi::security',
'strapi::cors',
'strapi::poweredBy',
'strapi::cors',
'strapi::logger',
'strapi::query',
'strapi::body',
'strapi::session',
'strapi::favicon',
'strapi::public',
'global::ctx-decrypt',
];
it is giving me error like this
0|super-fu | [2022-12-23 02:48:51.181] debug: βοΈ Server wasn't able to start properly.
0|super-fu | [2022-12-23 02:48:51.183] error: Middleware "global::ctx-decrypt": Cannot destructure property 'strapi' of 'undefined' as it is undefined.
0|super-fu | Error: Middleware "global::ctx-decrypt": Cannot destructure property 'strapi' of 'undefined' as it is undefined.
0|super-fu | at instantiateMiddleware (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/services/server/middleware.js:12:11)
0|super-fu | at resolveMiddlewares (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/services/server/middleware.js:56:18)
0|super-fu | at registerApplicationMiddlewares (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/services/server/register-middlewares.js:66:29)
0|super-fu | at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
0|super-fu | at async Object.initMiddlewares (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/services/server/index.js:99:7)
0|super-fu | at async Strapi.bootstrap (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/Strapi.js:414:5)
0|super-fu | at async Strapi.load (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/Strapi.js:426:5)
0|super-fu | at async Strapi.start (/home/blackhole/nodejs/super-funnels/super-funnels-backend/node_modules/#strapi/strapi/lib/Strapi.js:169:9)
0|super-fu |
0|super-fu | > super-funnels-backend#0.1.0 start
0|super-fu | > strapi start
How can I resolve this and use this middleware?
According to the docs, the correct function definition for middleware is as follows:
module.exports = (config, { strapi })=> {
return (context, next) => {};
};
There is no third parameter so perhaps try {env, strapi}?
Discord.js won't register my commands again, despite the fact they don't exist. I've verified that the commands don't exist by running:
rest.get(Routes.applicationCommands(clientId))
.then(data => console.log(data));
with the only response being []
The error I get is this:
DiscordAPIError[50035]: Invalid Form Body
method: 'PUT',
url: 'https://discord.com/api/v9/applications/985234455717363762/commands',
requestBody: {
files: undefined,
json: [
[Object], [Object],
[Object], [Object],
[Object], [Object],
[Object], [Object]
]
}
}
The code (deploy-commands.js) looks like this:
const fs = require('node:fs');
const path = require('node:path');
const { REST } = require('#discordjs/rest');
const { Routes } = require('discord-api-types/v9');
const { clientId, token } = require('./config.json');
const commands = [];
const commandsPath = path.join(__dirname, 'commands');
const commandFiles = fs.readdirSync(commandsPath).filter(file => file.endsWith('.js'));
for (const file of commandFiles) {
const filePath = path.join(commandsPath, file);
const command = require(filePath);
commands.push(command.data.toJSON());
}
const rest = new REST({ version: '9' }).setToken(token);
rest.put(Routes.applicationCommands(clientId), { body: commands })
.then(() => console.log('Successfully registered application commands.'))
.catch(console.error);
With this filetree:
discord-bot/
|-- Commands/
|-- avatar.js
|-- beep.js
|-- kick.js
|-- options-info.js
|-- ping.js
|-- prune.js
|-- server.js
|-- user-info.js
|-- node_modules
|-- config.json
|-- deploy-commands.js
|-- index.js
|-- package-lock.json
|-- package.json
HELP!
This error comes if you have 2 application commands with the same name, double check your command names and make sure every command has a unique name.
I am testing a nodejs express API with a PG db wrapped with Prisma ORM
I configured a testing singleton instance as described by Prisma docs
Since the API is implemented in CommonJS and not TS, I had to make some changes as described in this beautiful page.
Here is a synthesis of what I did, will try to make it short, so it's easier to read
orgs.js (A GET route served by the mock server later on ...)
const Router = require('express-promise-router')
const router = new Router()
const PrismaPool = require('../db/PrismaPool');
module.exports = router
router.get('/assessments', async (req, res) => {
try{
const prisma = PrismaPool.getInstance();
const data = await prisma.org.findUnique({
select:{
assessments:true,
},
where: {
id: res.locals.orgId,
},
})
res.send(data)
}
catch(err){
handleError(err, "[GET]/orgs/assessments", 400, req, res)
}
})
PrismaPool.js (A wrapper to access the unique prisma client instance)
const prisma = require('./PrismaClientInstance').default
class PrismaPool {
constructor() {
throw new Error('Use PrismaPool.getInstance()');
}
static getInstance() {
return prisma
}
}
module.exports = PrismaPool;
PrismaClientInstance.js The unique instance of PrismaClient class. This is the tricky part stitching between the CommonJS world and the TS world.
'use strict';
exports.__esModule = true;
const { PrismaClient } = require('#prisma/client')
const prisma = new PrismaClient()
exports['default'] = prisma;
All this configuration works GREAT at runtime, now, when wrapping it with JEST in unit tests, things go south quickly ...
mock_server.js (a simplified server to expose the orgs API above)
const http = require('http');
const express = require('express');
var orgsRouter = require('../orgs');
const app = express();
app.use('/orgs', orgsRouter);
const port = 3011
app.set('port', port);
const server = http.createServer(app);
function onError(error) {
// herror handling
}
function onListening() {
// some debug messages
}
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
module.exports = server
PrismaSingletonForTesting.ts (A jest deep mock of the PrismaClient instance)
import { PrismaClient } from '#prisma/client'
import { mockDeep, mockReset, DeepMockProxy } from 'jest-mock-extended'
import prisma from './PrismaClientInstance'
jest.mock('./PrismaClientInstance', () => ({
__esModule: true,
default: mockDeep<PrismaClient>()
}))
beforeEach(() => {
mockReset(prismaMock)
})
export const prismaMock = prisma as unknown as DeepMockProxy<PrismaClient>
orgs.test.js (The tests of the orgs API)
const Request = require("request")
const { prismaMock } = require('../../db/PrismaSingletonForTesting')
const TEST_ORGID = 1
describe('egrest server', () => {
let server
beforeAll(() => {
server = require('./test_server')
})
afterAll(() => {
server.close()
})
describe('assessments', () => {
let data = {}
beforeAll(() => {
const testorg = {
id: TEST_ORGID,
name: 'jestest',
admin:33,
avail_tests: 1234
}
prismaMock.org.findUnique.mockResolvedValue(testorg)
})
it(`read remaining assessments for org ${TEST_ORGID}`, (done) => {
Request.get("http://localhost:3011/orgs/assessments", (error, response, body) => {
data.status = response.statusCode
data.body = body
data.error = error
console.dir(body)
done()
})
})
})
})
I also configured .jest.config with the required line setupFilesAfterEnv: ['./db/PrismaSingletonForTesting.ts']
When I run this test, I get data=undefined in orgs.js, even-though I mocked prisma.org.findUnique by doing prismaMock.org.findUnique.mockResolvedValue(testorg) as described by prisma docs.
Any help would be appreciated.
I have a complicated task to do. I need to separate my sequelize models in separate folders inside the models folder, just like this structure:
βββ node_modules
βββ src
| βββ models
| βββsettings
| | βββ user.js
| | βββ location.js
| βββstock
| | βββ stock.js
| | βββ products.js
| βββ index.js
Today, I have all models in models root folder, together with index.js, in a way that I can call every model from controllers as
const { users, stock } = require("../../models")
The code inside index.js is like this:
"use strict"
const fs = require("fs")
const path = require("path")
const Sequelize = require("sequelize")
const basename = path.basename(__filename)
const env = process.env.NODE_ENV || "development"
const envConfigs = require("../config/config")
const config = envConfigs[env]
const db = {}
let sequelize
if (config.url) {
sequelize = new Sequelize(config.url, config)
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
)
}
fs.readdirSync(__dirname)
.filter((file) => {
return (
file.indexOf(".") !== 0 && file !== basename && file.slice(-3) === ".js"
)
})
.forEach((file) => {
const model = require(path.join(__dirname, file))(
sequelize,
Sequelize.DataTypes
)
db[model.name] = model
})
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
db.sequelize = sequelize
module.exports = db
This was automaticly created using sequelize-cli.
I think that I need some search inside index.js and concatenate folders and files into Model variable inside index.js, in such a way that I can use the models like "model.settings.user" to do associations or calling the objects in controllers.
Finally, I was able to get the index.js working properly! This is the code for anyone that need it (having this code in the index.js, it will search in all folders and put all models name files into const "model"
"use strict"
const fs = require("fs")
const path = require("path")
const Sequelize = require("sequelize")
const basename = path.basename(__filename)
const env = process.env.NODE_ENV || "development"
const envConfigs = require("../config/config")
const config = envConfigs[env]
const db = {}
let sequelize
if (config.url) {
sequelize = new Sequelize(config.url, config)
} else {
sequelize = new Sequelize(
config.database,
config.username,
config.password,
config
)
}
const files = []
const sortDir = (maniDir) => {
const folders = []
const CheckFile = (filePath) => fs.statSync(filePath).isFile()
const sortPath = (dir) => {
fs.readdirSync(dir)
.filter((file) => file.indexOf(".") !== 0 && file !== "index.js")
.forEach((res) => {
const filePath = path.join(dir, res)
if (CheckFile(filePath)) {
files.push(filePath)
} else {
folders.push(filePath)
}
})
}
folders.push(maniDir)
let i = 0
do {
sortPath(folders[i])
i += 1
} while (i < folders.length)
}
sortDir(__dirname)
files.forEach((file) => {
const model = require(file)(sequelize, Sequelize.DataTypes)
db[model.name] = model
})
Object.keys(db).forEach((modelName) => {
if (db[modelName].associate) {
db[modelName].associate(db)
}
})
db.sequelize = sequelize
module.exports = db
Can I use only JEST to test my rest API endpoints in express? I've searched several articles and also saw some of the questions of stackoverflow to see how can I do this. But the problem is for testing in express most of the people are using mocha/chai or supertest. But I want to use only JEST in my test. Is this possible?
this is my code so far where I want to implement this test:
index.js
const express = require('express');
const app = express();
app.post('/insert', function (req, res, next) {
const values = req.body; //name, roll
pool.query(`INSERT INTO student SET ?`, [values], (err, result) => {
if (err){
let err = new Error('Not Connected');
next(err);
} else {
res.status(201).json({ msg: `added ${result.insertId}`});
console.log(result);
}
});
});
what i've tried so far is :
index.test.js:
const express = require('express');
const app = express();
app.use('../routes');
test('Test POST, Success Scenario', async () => {
const response = await app.post('/insert')({
const values //dummy values will be insert here
});
expect(response.statusCode).toBe(200);
});
I know my test code is not correct, it's just a pseudocode I'm actually confused how I will hit the end point here
Here is the unit test solution for testing Nodejs web framework express REST API ONLY USING JEST:
index.js:
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool();
app.post('/insert', (req, res, next) => {
const values = req.body;
pool.query(`INSERT INTO student SET ?`, [values], (err, result) => {
if (err) {
err = new Error('Not Connected');
next(err);
} else {
res.status(201).json({ msg: `added ${result.insertId}` });
console.log(result);
}
});
});
index.spec.js:
const routes = {};
jest.mock('express', () => {
const mExpress = {
post: jest.fn((path, controller) => {
routes[path] = controller;
})
};
return jest.fn(() => mExpress);
});
let queryCallback;
jest.mock('pg', () => {
const mpool = {
query: jest.fn((query, values, callback) => {
queryCallback = callback;
})
};
const mPool = jest.fn(() => mpool);
return { Pool: mPool };
});
require('./index');
const express = require('express');
const { Pool } = require('pg');
const app = express();
const pool = new Pool();
describe('insert', () => {
afterEach(() => {
jest.restoreAllMocks();
});
test('should insert data correctly', done => {
const logSpy = jest.spyOn(console, 'log');
expect(app.post).toBeCalledWith('/insert', expect.any(Function));
const mReq = { body: 1 };
const mRes = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() };
routes['/insert'](mReq, mRes);
expect(pool.query).toBeCalledWith('INSERT INTO student SET ?', [1], expect.any(Function));
const mResult = { insertId: 1 };
queryCallback(null, mResult);
expect(mRes.status).toBeCalledWith(201);
expect(mRes.status().json).toBeCalledWith({ msg: 'added 1' });
expect(logSpy).toBeCalledWith(mResult);
done();
});
test('should call error handler middleware', () => {
expect(app.post).toBeCalledWith('/insert', expect.any(Function));
const mReq = { body: 1 };
const mRes = { status: jest.fn().mockReturnThis(), json: jest.fn().mockReturnThis() };
const mNext = jest.fn();
routes['/insert'](mReq, mRes, mNext);
expect(pool.query).toBeCalledWith('INSERT INTO student SET ?', [1], expect.any(Function));
const mError = new Error('network error');
queryCallback(mError, null);
expect(mNext).toBeCalledWith(new Error('Not Connected'));
});
});
Unit test result with 100% coverage:
PASS src/stackoverflow/56635460/index.spec.js (7.391s)
insert
β should insert data correctly (15ms)
β should call error handler middleware (1ms)
console.log node_modules/jest-mock/build/index.js:860
{ insertId: 1 }
----------|----------|----------|----------|----------|-------------------|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s |
----------|----------|----------|----------|----------|-------------------|
All files | 100 | 100 | 100 | 100 | |
index.js | 100 | 100 | 100 | 100 | |
----------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 8.571s
Source code: https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/56635460