Skip to content

Commit

Permalink
Add Apollo Server example
Browse files Browse the repository at this point in the history
  • Loading branch information
jpmonette committed Apr 23, 2018
1 parent 6b7ccb3 commit 31eb466
Show file tree
Hide file tree
Showing 10 changed files with 459 additions and 113 deletions.
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

0 comments on commit 31eb466

Please sign in to comment.