Express/GraphQL advice - optimization

I'm learning GraphQL and looking for some guidance on if I'm on the right track, or if I'm just completely off base here. Essentially, I've written my backend with express-graphql and Prisma, I've seen a few different tutorials that seemed to write the types and resolvers separately using large template strings to define the schema. I followed the Net Ninja's GraphQL tutorial which used new GraphQlObjectType() to build the schema and imported all the different types into a single root query, which is then passed into the GraphQlSchema()
My confusion and question: Is this the right way to be doing this?
What I've ended up with is a very long schema file that is functional and working, but not ideal for maintenance. I've struggled to find any examples online on how to separate this code and still have it functional. I've tried exporting a file of just GraphQL object types and importing them into a schema.js file and then building my root query there but that doesn't seem to work. I've tried exporting only the fields as an object and then using the spread operator to assemble my root query, and I've tried multiple new GraphQlSchema() statements within my type files and then using graphql-tools mergeSchema to bring it all together but that didn't work either. I'm a little lost at this point and just need some guidance or an example to look at of a bigger project built in this way.
For reference, here's a snippet example to be clear on how I've built my GQL backend, to clarify the queries are working just fine, it's code maintenance here I'm gratefully looking for advice on :)
Thank you for any help or examples!
const prisma = require("../config/prismaClient");
const graphql = require('graphql');
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLString,
GraphQLList } = graphql;
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
id: { type: GraphQLString },
first_name: { type: GraphQLString },
last_name: { type: GraphQLString },
})
})
const BusinessType = new GraphQLObjectType({
name: 'Business',
fields: () => ({
id: { type: GraphQLString },
name: { type: GraphQLString },
street_address: { type: GraphQLString },
})
})
const RootQuery = new GraphQLObjectType({
name: 'RootQueryType',
fields: {
users: {
type: new GraphQLList(UserType),
async resolve() {
return await prisma.users.findMany();
}
},
businesses: {
type: new GraphQLList(BusinessType),
async resolve() {
return await prisma.businesses.findMany();
}
}
}
})
module.exports = new GraphQLSchema({
query: RootQuery
})

My confusion and question: Is this the right way to be doing this?
There is no right or wrong way of writing you schema. You can use SDL (Schema definition language) which is very readable but then you need to add some dependencies to your project to set this up. GraphQL Tools has very good utilities that make it easy to write you schema.
Yes it can be little overwhelming when trying to setup these tools. I like splitting my schema and resolvers in a directory based on Type and then graphql-tools helps me merge all the resolvers and schema together into one.
I found this project https://github.com/lucassus/bookshelf take a look at how they structured there graphql server https://github.com/lucassus/bookshelf/tree/master/apps/server/src/interfaces/graphql

Related

Fastify equivalent of express-mongo-sanitize

Hello Fastify Experts,
In MongoDB queries I can pass various operators, which may risks the security aspect by having various attack surfaces.
So before sending the payload, I would like to sanitize the query/filters/sort etc. However I don't think I need to sanitize the request payload as such because Mongo will anyway store it as BSON, hence safer.
Now in Express world, we used to have the express-mongo-sanitize sort of plugin.
What open source plugin you propose for Fastify world to achieve the similar functionality?
Thanks,
Pradip
You have two options:
use the schema eviction: adding additionalProperties as flag into the input schema, will remove all the keys you did not expect from input
With this code, you can submit a payload with:
{
foo: 'hello',
$where: 'cut'
}
and the $where key will be removed.
const fastify = require('fastify')({ logger: true })
fastify.post('/', {
schema: {
body: {
type: 'object',
additionalProperties: false,
properties: {
foo: { type: 'string' }
}
}
}
},
async (request, reply) => {
console.log(request.body)
return request.body
})
fastify.listen(8080)
The framework you linked has a module feature and you can integrate it with an hook:
const mongoSanitize = require('express-mongo-sanitize');
fastify.addHook('preHandler', function hook (request, reply, done) {
mongoSanitize.sanitize(request.body);
done(null)
})

cannot use the find functions using sequelize-typescript

I have setup passport and sequelize-typescript for my project. In the passport setup, I use a strategy for example google like this:
passport.use(
new GoogleStrategy(
{
clientID: process.env.GOOGLE_AUTH_CLIENT_ID,
clientSecret: process.env.GOOGLE_AUTH_CLIENT_SECRET,
callbackURL: process.env.GOOGLE_AUTH_CALLBACK_URL,
profileFields: ['id', 'displayName', 'photos', 'email'],
enableProof: true
},
function(accessToken, refreshToken, profile, done) {
console.log(profile)
const { name, email, picture } = profile._json;
User.findOne({where: {id: profile.id}})
.then(user => {
console.log(user)
if(user === null) {
const { name, email, picture } = profile._json;
// new User({
// id: profile.id,
// name: name,
// email: email,
// pictureUrl: picture,
// })
}
})
done(null, profile)
}
)
)
When I try to use functions such as findOrCreate() or findOne(), I receive a typescript error that says:
[ERROR] 23:29:01 ⨯ Unable to compile TypeScript:
src/passport_strategies.ts:45:18 - error TS2339: Property 'findOne' does not exist on type 'typeof User'.
45 User.findOne({where: {id: profile.id}})
I also get the same error for the part commented out in the first code snippet. The model user I have created is declared like this:
export class User extends Model<User> {} (It has the columns set in the file) Model being imported from sequelize-typescript
Here is where sequelize is created:
export const sequelize = new Sequelize({
"username": c.username,
"password": c.password,
"database": c.database,
"host": c.host,
dialect: 'postgres',
storage: ':memory:',
models: [__dirname + '/models']
});
I tried checking other examples that are on the internet but they all have the same setup and I couldn't figure out why I'm getting this error. Not sure if this helps at all but I'm using postgres dialect.
I suspect that it is a version mismatch.
sequelize-typescript#2 is for sequelize#6.2>= and sequelize-typescript#1 is for sequelize#5>=.
I also suggest for educational purposes to implement typescript with sequelize without the use of the sequelize-typescript package just for understanding the need of the package itself. https://sequelize.org/master/manual/typescript.html
Also just in case with all the respect, i point that #Table is needed if you are using the latest version.

Changing a nested object in Redux using spread operators when the keys are dynamic?

I am trying to store a nested object in redux, but I am using dynamic keys. Here is an example of what it would look like:
// In my Redux Reducer
const initialState = {
stuff: {
<dynamic_key>: { name: 'bob', title: 'mr' },
<dynamic_key>: { name: 'eve', title: 'ms' },
<dynamic_key>: { name: 'car', title: 'na' },
},
};
So I have a redux state called stuff that should hold my nested objects.
However, I cannot correctly save my data. I know react states all have to be immutable, so I am using the spread operator and Object.assign() to create a new object:
const reducer = ( state = initialState, action) => {
// ....
case UPDATE:
return { ...state,
stuff: {
Object.assign({}, action.key, {
name: action.name,
title: action.title
})
}
};
// ....
}
The above is trying to create/update the entire <dynamic_key>, using action.key as the dynamic_key, action.name as the name, and action.title as the title.
An extra tidbit is that if action.key (the dynamic key) doesn't already exist in the redux store stuff, then it should be created rather than error out.
What am I doing wrong? I think I am not using Object.assign() correctly, or not using the spread operator correctly?
EDIT:
Here is my redux action:
export const update = s => ({ type: "UPDATE", payload: {key: s.key, name: s.name, title: s.title} });
Using object spread operator
It seems like in you're case, you've got a couple additionally unnecessary steps. If stuff is supposed to be an object that contains your dynamic keys/value pairs, then you should have:
stuff: Object.assign({}, state.stuff, {[action.key]: {etc...}})
OR
stuff: {
...state.stuff
[action.key]: {name: etc...}
}
Keep in mind that every argument to Object.assign, must be an object. It seems like you are supplying the second argument a string.
I also assume you already have a compiler that allows you to safely use the object spread syntax.
EDIT: added state.stuff to both examples so as not to override previous properties.

Understanding hooks in Sequelize (Returning vs. Attaching)

I am following my school's workshop regarding how to integrate Sequelize with Express. There is a section where we are learning to leverage hooks in our models—and in it I was confused by this:
Returning vs. Attaching
A hook runs with the instance of a Page being
saved given as an argument. We want to, therefore, attach a created
urlTitle to this page instance instead of returning it from the
function.
var Sequelize = require('sequelize');
var db = new Sequelize('postgres://localhost:5432/__wikistack__', {
logging: false,
});
const Page = db.define(
'page',
{
title: {
type: Sequelize.STRING,
},
urlTitle: {
type: Sequelize.STRING,
},
content: {
type: Sequelize.TEXT,
},
status: {
type: Sequelize.ENUM('open', 'closed'),
},
},
{
hooks: {
beforeValidate: function(page) {
if (page.title) {
// Removes all non-alphanumeric characters from title
// And make whitespace underscore
return (page.urlTitle = page.title.replace(/\s/g, '_').replace(/\W/g, ''));
} else {
// Generates random 5 letter string
return (urlTitle = Math.random()
.toString(36)
.substring(2, 7));
}
},
},
}
);
Can someone explain this? How can the function in the hook not return something? The above works, so the hook/function is returning something.
Thanks in advance!
Hooks are just code that gets run at certain life cycle points of a record instance. You can have them be purely side effects. In your case, all you need to do is modify the page object that the hook is passed, return doesn't help or hurt.
However, the return value of a hook is not useless. If you need to do anything async inside a hook, you have to return a promise.

Ember, Ember Data - Updating hasMany relation

I'm trying to update a hasMany relation in Ember but I'm having a bit of trouble getting the data to send correctly.
I have the following Ember models:
// models/post.js
export default DS.Model.extend({
title: DS.attr('string'),
tags: DS.hasMany('tag', { async: true })
});
// models/tag.js
export default DS.Model.extend({
title: DS.attr('string'),
post: DS.belongsTo('post', { async: true })
});
And then I have the following action in my route to create a new tag and update the post:
addTag: function() {
var post = this.get('currentModel');
var newTag = this.store.createRecord('tag', {
title: 'Lorem Ipsum',
post: post
});
newTag.save().then(function() {
post.get('tags').pushObject(newTag);
post.save();
});
}
The new tag is created successfully and saved by my Express api but the post doesn't get saved correctly. It's received by the api but the request payload made by Ember never contains the tag IDs (it does send the tag title though). What am I doing wrong? Would really appreciate any help!
Edit
It turns out the RESTSerializer by default doesn't serialize and include the related IDs for a hasMany relationship. It only includes them for the belongsTo side as it expects the API to take care of saving it where needed. I will probably change my API to fit this behaviour as it's more efficient but in case any one else comes across this, it is possible to make the serializer include the IDs by extending the serializer and using the DS.EmbeddedRecordsMixin mixin - http://emberjs.com/api/data/classes/DS.EmbeddedRecordsMixin.html - Which would look something like this:
export default DS.RESTSerializer.extend(DS.EmbeddedRecordsMixin, {
attrs: {
tags: { serialize: 'ids' }
}
});
You don't need to call .save() on post. When you call createRecord to create a tag, your backend receives id of post and should persist dependencies accordingly.
addTag: function() {
var post = this.get('currentModel');
this.store.createRecord('tag', {
title: 'Lorem Ipsum',
post: post})
.save()
.then(function(tag) {
post.get('tags').pushObject(tag);
});
Meet the same problem.
Currently I solve it by serializeHasMany hook.
// app/serializers/application
import {ActiveModelSerializer} from 'active-model-adapter';
export default ActiveModelSerializer.extend({
serializeHasMany: function(snapshot, json, relationship){
this._super.apply this, arguments
if (!json[relationship.type + '_ids']){
var ids = []
snapshot.record.get(relationship.key).forEach(function(item){
ids.push item.get 'id'
});
json[relationship.type + '_ids'] = ids
}
}
})