ParseServerSwift
Write Cloud Code in Swift!
What is Cloud Code? For complex apps, sometimes you just need logic that isn’t running on a mobile device. Cloud Code makes this possible. Cloud Code in ParseServerSwift is easy to use because it’s built using Parse-Swift and Vapor. The only difference is that this code runs in your ParseServerSwift rather than running on the user’s mobile device. When you update your Cloud Code, it becomes available to all mobile environments instantly. You don’t have to wait for a new release of your application. This lets you change app behavior on the fly and add new features faster.
Configure ParseServerSwift
To configure, you should edit ParseServerSwift/Sources/ParseServerSwift/configure.swift
WebhookKey
The webhookKey
should match the webhookKey on the Parse Server. If you decide not the a webhookKey
, set the value to nil
in your ParseServerSwift.
Hostname, Port, and TLS
By default, the hostname is 127.0.0.1
and the port is 8081
. These values can easily be changed:
app.http.server.configuration.hostname = "your.hostname.com"
app.http.server.configuration.port = 8081
app.http.server.configuration.tlsConfiguration = .none
Parse Swift SDK
Configure the SDK as described in the documentation.
// Required: Change to your Parse Server serverURL.
guard let parseServerUrl = URL(string: "http://localhost:1337/1") else {
throw ParseError(code: .unknownError,
message: "Could not make Parse Server URL")
}
// Initialize the Parse-Swift SDK
ParseSwift.initialize(applicationId: "applicationId", // Required: Change to your applicationId.
clientKey: "clientKey", // Required: Change to your clientKey.
masterKey: "masterKey", // Required: Change to your masterKey.
serverURL: parseServerUrl) { _, completionHandler in
completionHandler(.performDefaultHandling, nil)
}
Starting the Server
To start your server type, swift run
in the terminal of the project root directory.
Adding ParseObject
‘s
It is recommended to add all of your ParseObject
‘s to ParseServerSwift/Sources/ParseServerSwift/Models. An example GameScore
model is provided:
import Foundation
import ParseSwift
/**
An example `ParseObject`. This is for testing. You can
remove when creating your application.
*/
struct GameScore: ParseObject {
// These are required by ParseObject.
var objectId: String?
var createdAt: Date?
var updatedAt: Date?
var ACL: ParseACL?
var originalData: Data?
// Your own properties.
var points: Int?
// Implement your own version of merge.
func merge(with object: Self) throws -> Self {
var updated = try mergeParse(with: object)
if updated.shouldRestoreKey(\.points,
original: object) {
updated.points = object.points
}
return updated
}
}
The ParseUser
Model
Be sure to add all of the additional properties you have in your _User
class to the User
model which is located at ParseServerSwift/Sources/ParseServerSwift/Models/User.swift
Parse Cloud Code Hook Routes
Adding routes for ParseHooks are as simple as adding routes in Vapor. ParseServerSwift
adds some additional methods to routes to easily create and register Hook Functions and Hook Triggers. All routes should be added to ParseServerSwift/Sources/ParseServerSwift/routes.swift
Cloud Code Functions
Cloud Code Functions can also take parameters. It’s recommended to place all paramters in ParseServerSwift/Sources/ParseServerSwift/Models/Parameters
app.post("foo",
name: "foo") { req async throws -> ParseHookResponse<String> in
if let error: ParseHookResponse<String> = checkHeaders(req) {
return error
}
var parseRequest = try req.content
.decode(ParseHookFunctionRequest<User, FooParameters>.self)
// If a User called the request, fetch the complete user.
if parseRequest.user != nil {
parseRequest = try await parseRequest.hydrateUser()
}
// To query using the User's credentials who called this function,
// use the options() method from the request
let options = parseRequest.options()
let scores = try await GameScore.query.findAll(options: options)
req.logger.info("Scores this user can access: \(scores)")
return ParseHookResponse(success: "Hello, new world!")
}
Cloud Code Triggers
// A Parse Hook Trigger route.
app.post("bar",
className: "GameScore",
triggerName: .beforeSave) { req async throws -> ParseHookResponse<GameScore> in
if let error: ParseHookResponse<GameScore> = checkHeaders(req) {
return error
}
var parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
// If a User called the request, fetch the complete user.
if parseRequest.user != nil {
parseRequest = try await parseRequest.hydrateUser()
}
guard let object = parseRequest.object else {
return ParseHookResponse(error: .init(code: .missingObjectId,
message: "Object not sent in request."))
}
// To query using the masterKey pass the `useMasterKey option
// to ther query.
let scores = try await GameScore.query.findAll(options: [.useMasterKey])
req.logger.info("All scores: \(scores)")
return ParseHookResponse(success: object)
}
// A Parse Hook Trigger route.
app.post("find",
className: "GameScore",
triggerName: .beforeFind) { req async throws -> ParseHookResponse<[GameScore]> in
if let error: ParseHookResponse<[GameScore]> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A query is being made: \(parseRequest)")
let score1 = GameScore(objectId: "yolo",
createdAt: Date(),
points: 50)
let score2 = GameScore(objectId: "yolo",
createdAt: Date(),
points: 60)
return ParseHookResponse(success: [score1, score2])
}
// A Parse Hook Trigger route.
app.post("login",
className: "_User",
triggerName: .afterLogin) { req async throws -> ParseHookResponse<Bool> in
if let error: ParseHookResponse<Bool> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A user has logged in: \(parseRequest)")
return ParseHookResponse(success: true)
}
// A Parse Hook Trigger route.
app.post("file",
triggerName: .afterDelete) { req async throws -> ParseHookResponse<Bool> in
if let error: ParseHookResponse<Bool> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A ParseFile is being saved: \(parseRequest)")
return ParseHookResponse(success: true)
}
// A Parse Hook Trigger route.
app.post("connect",
triggerName: .beforeConnect) { req async throws -> ParseHookResponse<Bool> in
if let error: ParseHookResponse<Bool> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A LiveQuery connection is being made: \(parseRequest)")
return ParseHookResponse(success: true)
}
// A Parse Hook Trigger route.
app.post("subscribe",
className: "GameScore",
triggerName: .beforeSubscribe) { req async throws -> ParseHookResponse<Bool> in
if let error: ParseHookResponse<Bool> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A LiveQuery subscribe is being made: \(parseRequest)")
return ParseHookResponse(success: true)
}
// A Parse Hook Trigger route.
app.post("event",
className: "GameScore",
triggerName: .afterEvent) { req async throws -> ParseHookResponse<Bool> in
if let error: ParseHookResponse<Bool> = checkHeaders(req) {
return error
}
let parseRequest = try req.content
.decode(ParseHookTriggerRequest<User, GameScore>.self)
req.logger.info("A LiveQuery event occured: \(parseRequest)")
return ParseHookResponse(success: true)
}