Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Apollo Server example #7

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
119 changes: 8 additions & 111 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,116 +14,13 @@
$ yarn add salesforce-graphql jsforce
```

### Example
## Examples

#### `app.ts`
Refer to the `examples` folder for examples:

```ts
import * as jsforce from 'jsforce';
import * as path from 'path';
import * as fs from 'fs';
> https://github.com/jpmonette/salesforce-graphql/tree/master/examples/apollo.

import { GraphQLServer } from 'graphql-yoga';
import { Binding } from 'salesforce-graphql';

const schemaFile = path.join(__dirname, 'schema.graphql');
const typeDefs = fs.readFileSync(schemaFile, 'utf8');

const { USERNAME, PASSWORD } = process.env;

const resolvers = {
Query: {
Accounts: (parent, args, context, info) =>
context.db.query({}, info).then(res => res.records),
Account: (parent, args, context, info) =>
context.db.query({}, info).then(res => res.records[0]),
Contacts: (parent, args, context, info) =>
context.db.query({}, info).then(res => res.records),
Contact: (parentobj, args, context, info) =>
context.db.query({}, info).then(res => res.records[0]),
},
Account: {
Contacts: (parent, args, context, info) =>
context.db.query({ AccountId: parent.Id }, info).then(res => res.records),
},
Contact: {
Account: (parent, args, context, info) =>
context.db.query({ Id: parent.AccountId }, info).then(res => res.records[0]),
},
};

const conn = new jsforce.Connection({});

function init() {
const db = new Binding({ conn });

const server = new GraphQLServer({
typeDefs,
resolvers,
context: req => ({ ...req, db }),
});

server.start({ playground: '/playground' }, ({ port }) =>
console.log('Server is running on localhost:' + port)
);
}

conn.login(USERNAME, PASSWORD, (err, userinfo) => init());
```

#### `schema.graphql`

```graphql
type Query {
Account(Id: ID!): Account
Accounts(limit: Int): [Account]
Contact(Id: ID!): Contact
Contacts(limit: Int): [Contact]
}

type Account {
Id: ID!
IsDeleted: Boolean
Name: String
Type: String

Contacts(limit: Int): [Contact]
}

type Contact {
Id: ID!
Account: Account
AccountId: String
LastName: String
FirstName: String
Salutation: String
Name: String
}
```

When you are ready, start the GraphQL server:

```sh
$ yarn start
```

Head over to `http://localhost:4000/playground` to test with the following query:

```graphql
{
Account(Id: "001E000001KnMkTIAV") {
Id
Name
Contacts(limit: 1) {
Name
AccountId
Account {
Name
}
}
}
}
```
## Sample GraphIQL Output

![Sample Output](assets/output.png)

Expand All @@ -135,10 +32,10 @@ Head over to `http://localhost:4000/playground` to test with the following query

## References

- [`salesforce-graphql` on NPM](https://www.npmjs.com/package/salesforce-graphql)
- Learn more about [GraphQL](http://graphql.org/)
- [Salesforce REST API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_what_is_rest_api.htm) documentation
* [`salesforce-graphql` on NPM](https://www.npmjs.com/package/salesforce-graphql)
* Learn more about [GraphQL](http://graphql.org/)
* [Salesforce REST API](https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/intro_what_is_rest_api.htm) documentation

## Extra

- Looking for [new opportunities](https://mavens.com/careers/)? Have a look at [Mavens](https://mavens.com/) website!
* Looking for [new opportunities](https://mavens.com/careers/)? Have a look at [Mavens](https://mavens.com/) website!
27 changes: 27 additions & 0 deletions examples/apollo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "apollo",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "nodemon -e ts -x ts-node src/index.ts"
},
"dependencies": {
"apollo-server-express": "^1.3.5",
"body-parser": "^1.18.2",
"cookie-parser": "^1.4.3",
"cookie-session": "^2.0.0-beta.3",
"dataloader": "^1.4.0",
"dotenv": "^5.0.1",
"express": "^4.16.3",
"express-session": "^1.15.6",
"graphql": "^0.13.2",
"graphql-tools": "^2.24.0",
"jsforce": "^1.8.4",
"passport": "^0.4.0",
"passport-forcedotcom": "^0.1.4"
},
"devDependencies": {
"@types/node": "^9.6.6"
}
}
68 changes: 68 additions & 0 deletions examples/apollo/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { graphiqlExpress, graphqlExpress } from 'apollo-server-express';
import * as bodyParser from 'body-parser';
import * as cookieParser from 'cookie-parser';
import * as cookieSession from 'cookie-session';
import * as DataLoader from 'dataloader';
import * as express from 'express';
import { makeExecutableSchema } from 'graphql-tools';
import * as jsforce from 'jsforce';
import * as passport from 'passport';
import { Strategy } from 'passport-forcedotcom';

import { cookieOptions, strategyOptions } from './lib/config';
import typeDefs from './lib/typeDefs';
import resolvers from './resolvers';

const app = express();

passport.use(new Strategy(strategyOptions, (token, _1, _2, done) => done(null, token.params)));
app.use(cookieParser());
app.use(cookieSession(cookieOptions));
app.use(passport.initialize());
app.use(passport.session());

passport.serializeUser((token, done) => done(null, token));
passport.deserializeUser((token, done) => done(null, token));

app.get('/auth/salesforce', passport.authenticate('forcedotcom'));

app.get(
'/services/oauth2/callback',
passport.authenticate('forcedotcom', { failureRedirect: '/error' }),
(req, res) => res.redirect('/graphiql')
);

app.use('/graphiql', graphiqlExpress({ endpointURL: '/graphql' }));

const executeQuery = ({ access_token, instance_url }, query) =>
new jsforce.Connection({
version: '42.0',
accessToken: access_token,
instanceUrl: instance_url,
})
.query(query)
.then(response => response.records);

const insert = ({ access_token, instance_url }, sobjectName, data) =>
new jsforce.Connection({
version: '42.0',
accessToken: access_token,
instanceUrl: instance_url,
})
.sobject(sobjectName)
.create(data);

app.use('/graphql', bodyParser.json(), (req: any, res, next) => {
const queryLoader = new DataLoader(keys =>
Promise.all(keys.map(query => executeQuery(req.user, query)))
);

const db = { insert: (sobjectName, data) => insert(req.user, sobjectName, data) };

return graphqlExpress({
schema: makeExecutableSchema({ typeDefs: typeDefs, resolvers: resolvers as any }),
context: { user: req.user, loaders: { query: queryLoader }, db },
})(req, res, next);
});

app.listen(3000, () => console.log('Now browse to localhost:3000/graphiql'));
23 changes: 23 additions & 0 deletions examples/apollo/src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
require('dotenv').config();

export const {
SALESFORCE_USERNAME,
SALESFORCE_PASSWORD,
JWT_SECRET,
CALLBACK_URL,
CLIENT_ID,
CLIENT_SECRET,
} = process.env;

export const cookieOptions = {
name: 'session',
keys: ['key1'],
maxAge: 24 * 60 * 60 * 1000,
};

export const strategyOptions = {
clientID: CLIENT_ID,
clientSecret: CLIENT_SECRET,
scope: ['id', 'chatter_api', 'api'],
callbackURL: CALLBACK_URL,
};
89 changes: 89 additions & 0 deletions examples/apollo/src/lib/fieldLists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
export const contactFields = [
'AccountId',
'AssistantName',
'AssistantPhone',
'Birthdate',
'CreatedById',
'CreatedDate',
'CurrencyIsoCode',
'Department',
'Description',
'Email',
'EmailBouncedDate',
'EmailBouncedReason',
'Fax',
'FirstName',
'HomePhone',
'Id',
'IsDeleted',
'IsEmailBounced',
'Jigsaw',
'JigsawContactId',
'LastActivityDate',
'LastCURequestDate',
'LastCUUpdateDate',
'LastModifiedById',
'LastModifiedDate',
'LastName',
'LastReferencedDate',
'LastViewedDate',
'LeadSource',
'MailingAddress',
'MailingGeocodeAccuracy',
'MasterRecordId',
'MobilePhone',
'Name',
'OtherAddress',
'OtherGeocodeAccuracy',
'OtherPhone',
'OwnerId',
'Phone',
'PhotoUrl',
'ReportsToId',
'Salutation',
'SystemModstamp',
'Title',
];
export const accountFields = [
'AccountNumber',
'AccountSource',
'AnnualRevenue',
'BillingAddress',
'BillingGeocodeAccuracy',
'CreatedById',
'CreatedDate',
'CurrencyIsoCode',
'Description',
'Fax',
'Id',
'Industry',
'IsCustomerPortal',
'IsDeleted',
'IsPartner',
'Jigsaw',
'JigsawCompanyId',
'LastActivityDate',
'LastModifiedById',
'LastModifiedDate',
'LastReferencedDate',
'LastViewedDate',
'MasterRecordId',
'Name',
'NumberOfEmployees',
'OwnerId',
'Ownership',
'ParentId',
'Phone',
'PhotoUrl',
'Rating',
'RecordTypeId',
'ShippingAddress',
'ShippingGeocodeAccuracy',
'Sic',
'SicDesc',
'Site',
'SystemModstamp',
'TickerSymbol',
'Type',
'Website',
];
Loading